In addition to standard C malloc/realloc/free
functions, C++ has new/delete
operators for dynamic memory allocation. These are needed to properly handle
object construction and destruction. Obviously missing is a C++ equivalent of
realloc
. Namely, when realloc
can't grow memory block in-place, it needs to
copy it to new location. This copying is actually memcpy()
which might not
make sense for C++ objects defining copy constructor. Such objects must be
copied by invoking their copy-constructor.
It is not allowed to mix the two styles of memory allocation (although
new/delete
often just call malloc/free
to obtain raw memory). So the only
legal way to grow an array is to allocate the new (larger) array, invoke copy
constructor on each existing object, and deallocate the old array. Much of
this functionality is encapsulated in the default implementation-provided
allocator class, which underneath usually calls placement new and delete
operators.
std::vector
The vector STL container has to maintain elements contiguosly, and &v[0]
must give an address of the first element. Problem arises when a vector is
grown element by element, and the number of elements to be inserted is not
known in advance (otherwise, space could be allocated in advance with the
resize()
method). When the vector's allocated size is exceeded, the internal
storage array is grown as described above. This can amount to large copying
overhead, esp. if the objects are large or have complex copy
constructors. Some people have suggested use of the dequeue in such
cases. Currently, this indeed seems to be the best idea.
Many programs are executing in a virtual address space. Thus, realloc
doesn't really need to physically copy a chunk of memory, it just needs to
find a large enough range of virtual addresses and remap the current chunk
into the new range. This is how realloc()
in Linux glibc is implemented. The
amount of neccessary changes in system page tables is still proportional to
the size of allocated memory, but on much smaller scale. For example, with a
small page size (4kB), to grow 1-page chunk to 2 pages, one does not need to
copy 4096 bytes. Only one page table entry has to be modified. That's a great
saving. It gets even better with larger page sizes. (No, I haven't forgotten
the time to locate the larger range of virtual addresses. This work has to be
done in any case, copying or not). Unfortunately, the very useful mremap()
system call that realloc
internally is using to perform this remapping is
present only on Linux. No other popular Unix-like OS (Solaris, or *BSD)
implements it.
The deficiency of C++ standard allocator interface is that, although it is
supposed to manage just raw memory, it doesn't offer any mechanism to resize
the raw memory chunk. A temporary fix could be to partially specialize the
std::vector
template for PODs, in the following way: Make it use allocator
which calls malloc/free
instead of new/delete
. This is allowed for
POD-types. Override the resize()
method to call realloc()
Although
conceptually simple, this solution won't work. After looking through some STL
implementations, they directly manage memory instead of making a call to
resize()
method. I guess the C++ programmers are doomed to sub-optimal
performance in certain cases until this issue is satisfactorily resolved and
the mremap()
call (with Linux's semantic) becomes widespread. The annoying
thing is that these "certain cases" could have been opened to "clean"
optimization (not relying on implementation details) if the standard provided
some resizing methods in the allocator interface.