The purpose of a GMPAllocator
object is to enable a customized
strategy of memory management for the GMP arbitrary precision library. It
will give slightly improved run-time performance if your computation
involves many big integers of about the same size. By default, the memory
for numbers with up to about 40 decimal digits is handled specially. You
may also indicate the maximum size of integer to be handled in this way.
To use the CoCoA allocator for GMP values simply create an object of type
GMPAllocator
at the very start of your program; the object should
be destroyed only at the end of your program, after all GMP values have
been destroyed. Note: IT IS IMPERATIVE that you create the
GMPAllocator
object BEFORE creating the GlobalManager
.
Furthermore, no GMP values may be created (or even destroyed) after the
GMPAllocator
object has been destroyed. Failure to observe these
conditions will likely produce a crash (or worse).
By default a GMPAllocator
handles specially values fitting into 16 bytes.
You may specify a different size limit (in bytes) when you create the
GMPAllocator
, but when choosing the size limit be aware that each GMP
value below the specified size limit will occupy exactly the number of
bytes specified even if the number could fit into a smaller space. So if
you specify a large limit, you may waste lots of space if there are many
values much smaller than the limit.
You are advised to see the example program in examples/ex-GMPAllocator.C
If you have any doubts whatsoever about using a GMPAllocator
, don't! You
should not create more than one GMPAllocator
object.
Here are two examples uses in outline:
(1) Example of Default use of GMPAllocator.
{ GMPAllocator GMPMemMgr; // special mem mgt for GMPs fitting into 16 bytes GlobalManager CoCoAFoundations; ... // computations with GMP values ... }
(2) Example of use of GMPAllocator with user specified size limit.
{ GMPAllocator GMPMemMgr(100); // special mem mgt for GMPs up to 100 bytes GlobalManager CoCoAFoundations; ... // computations with GMP values ... }
The design of the implementation was to satisfy two needs: - (1) to allow faster GMP alloc/free for small GMP values; - (2) to permit monitoring of memory usage by GMP.
Aspect (1) is addressed by using a MemPool
to handle small memory
requests: any request which would fit into a single slice leads to
allocation of a whole slice (possibly wasting some space). The size of
a slice defines the meaning of small GMP value; it is specified
by the argument to the GMPAllocator
ctor which defaults to 16 (bytes).
See also a note in Bugs, Shortcomings, etc
Aspect (2) is partly addressed by the MemPool since a MemPool offers
monitoring facilities (when CoCoA_MEMPOOL_DEBUG
is positive).
The implementation is fairly straightforward. The basic idea is to use a
MemPool
to store all small GMP values, and to let the system allocator
handle space for larger values. Fortunately, the GMP library supplies the
block size even when deallocating (and the old block size when
reallocating), so it is easy to tell who had allocated the block.
Here is the reasoning behind the global variable GMPPoolPtr
. I have used
a file-local global variable to indicate which MemPool
to use because
this is only the way to pass the information to the alloc/free/realloc
functions. I have chosen to make the actual MemPool
object a
data member of GMPAllocator
so that I can control when its destructor is
called; this is important when gathering statistics in debug mode.
Making the MemPool
itself a global object would mean its destructor is
called only after main
has exited -- with no guarantees about what would
happen to the debugging output. Since the MemPool
object belongs to some
GMPAllocator
object, the global variable should be a non-owning pointer
with the convention that the pointer value be NULL when it points to no
object.
Of the alloc/free/realloc functions which are handed to GMP, only
CoCoA_GMP_realloc
displays any complication. GMP limbs can be stored
either in memory supplied by the MemPool
belonging to a GMPAllocator
object or in system allocated memory; a reallocation could cause the limbs
to be moved from one sort of memory to the other.
What is the best slice size to use? Too large a value may lead to
wastage of memory space, while too small a value will result in too many
requests being passed to the system allocator. I suspect that a
value between 8 and 16 (bytes) is probably a good choice on many platforms.
Now the (advanced) user can specify the slice size to use when creating
the GMPAllocator
object. Perhaps the size should be rounded up to the
next multiple of 4 (or even of 8).
The conceptual design is rather too fragile, but I do not see a robuster way. Part of the fragility seems to reside in the interface to GMP.
I do not like the use of a global variable (viz. GMPPoolPtr
), but it
seems inevitable. I'd rather not consider what would happen in a
multithreaded execution with potential race conditions, etc.
All code for debugging and gathering statistics is missing (except that
which is already included in MemPool
).
The destructor for GMPAllocator
could reset the GMP allocator functions to
the system ones; this might be useful for advanced users. Currently, the
code will follow a null pointer if someone attempts to allocate space for a
small GMP value after the destructor for GMPAllocator
has been called; this
is probably desirable behaviour for apprentices.
Is it possible to throw a std::bad_alloc
exception when GMP asks for too
much memory? How would this interact with the GMP library?