Daniel Shapero
Daniel Shapero

Reputation: 1889

are hidden globals bad programming practice?

I'm writing some linear algebra code (in Fortran 2003, but it would be the same issue in Fortran 90 or C) which requires a few work vectors to do computations in. My idea for dealing with this is to make a work array w(:,:) which is private to the linear algebra module, i.e. a "hidden global" as defined in this discussion of why true global variables are awful.

I imagine this as having a big problem to solve on a blackboard, and for each part of the problem you pick an area of the blackboard to solve it on.

In keeping with that analogy, I could also have a bunch of small whiteboards: define a work_array data type and pass them to the solvers as need be. (PETSc effectively uses this approach through another layer of abstraction; a solver is a data type which includes some procedure pointers to the methods used as well as a few work vectors.) When there are nested calls from one solver to the other, this gets a mite complicated, so I like the first way better. It also doesn't require as much misdirection.

Any thoughts on which approach makes for better programming practice?

EDIT: I also don't think it'll be a problem when I start using OpenMP, which I've already done in an old incarnation of this code. Each thread only accesses its portion of the unknowns and not those of other threads after the problem is set up. Nonetheless, concurrency issues are probably a good reason not to use static variables generally.

If I have to keep dynamically allocating space for the scratch arrays every time I call a solver, which is often, won't that incur a lot of overhead?

Upvotes: 5

Views: 329

Answers (3)

Bálint Aradi
Bálint Aradi

Reputation: 3812

As for the allocation cost: You can have a derived type containing all the work arrays (in your case the array w(:,:)). You can have one initialization call, which allocates them to the correct size, and then pass the derived type with the allocated arrays in it to the solver as often as possible, something in the following spirit:

module test
   implicit none

    type :: buffer
        integer, allocatable :: work(:,:)
    end buffer

contains

    subroutine init(mybuffer, whatever_else_you_need_for_determinig_allocation_size)
        type(buffer), intent(out) :: mybuffer

         allocate(mybuffer%work(dim1, dim2))
     end subroutine init

     subroutine solver(mybuffer, whatever_else_you_need_for_the_solver)
         type(buffer), intent(inout) :: mybuffer

          ! You can access mybuffer%work here as it is allocated

      end subroutine solver

end module test

But as it had been already pointed out, the allocation cost will be usually negligible with respect to the cost you spend in your solver.

Upvotes: 1

R.. GitHub STOP HELPING ICE
R.. GitHub STOP HELPING ICE

Reputation: 215261

If you're doing any non-trivial computation in the working space, the cost of malloc and free will be dominated by the cost of the computations performed in the allocated space, and the overhead will be approximately zero. The only time that avoiding allocation makes sense as an optimization strategy is when the amount of work performed on the buffer is so small that the time to obtain the buffer might dominate (or at least, might fail to be dominated by another term). The main situation where this can happen is building strings.

On the other hand, global state has a lot of costs:

  1. It precludes multi-threaded use.
  2. If the state needs to persists between multiple calls, it precludes library use (the library can't be used by more than one part of a program at a time since they might clobber each other's work).
  3. It precludes recursive/re-entrant use.
  4. It uses memory whenever the library is linked, even if the functions are never called.
  5. Even if you make efforts to work around some of these issues, it's a serious code smell and thus a cost in human time, i.e. it will waste the time of the next person who reads your code while they try to determine whether the global state is actually introducing any bugs or usage restrictions on the code.

Upvotes: 6

Sergey Kalinichenko
Sergey Kalinichenko

Reputation: 726559

The biggest danger of "hidden globals" (in C's world they are called static) comes when you write concurrent programs. Once you venture into multithreading, having a single blackboard is no longer sufficient: each thread needs its own one. For situations like that, dynamic allocation is more appropriate. If you do not worry about multithreading, having a module-scoped "hidden global" variable is perfectly fine.

Upvotes: 5

Related Questions