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?
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:
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With