January 93 - OOPC Memory Management
OOPC Memory Management
Gary Odom
The November 1992 issue of Frameworks had an article by Jeff Alger about memory
management in C++ with MacApp. Presented as a contrast to that article, this article
describes memory management with OOPC.
Programming with objects involves allocating thousands of blocks. By the time the
base classes in OOPC have been initialized, 7000 blocks have been allocated. Within a
few minutes of working with a document, 20,000 blocks or more have been allocated.
This is typical of object-oriented applications.
Macintosh Memory Management
The top-priority design goal for Macintosh memory management was to fully utilize
available memory (space efficiency), even at the expense of other considerations, such
as speed efficiency. This was sensible for the original Macintosh, a machine limited to
128 KB, but with relatively large memory demands. The chosen solution was to have
relocatable blocks of memory, accessed using pointers to pointers, called handles.
The danger with handles is using them. To access a block, the handle must be
dereferenced to its pointer. If the data is accessed a lot in a function, it is more
efficient to dereference a handle to its pointer, and repeatedly use the pointer. The
pointer is valid as long as the Memory Manager doesn't have a chance to move memory.
But if you call any function, you have to know whether that function can move memory.
If the function can move memory, you need to dereference the handle to get the pointer
again. If memory is moved, the master pointer may change, making the pointer you
were using invalid. The problem pointer is called a 'dangling pointer'.
Dangling pointers can be very hard to find, because memory may not move consistently
(it depends on how tight memory is at the time). You could be writing into an invalid
memory location and not discover a problem until much later. Dangling pointers are a
debugging nightmare.
The Macintosh Memory Manager keeps track of every allocated block. This means
memory management is optimized for a small number of blocks. With the relocatable
block memory scheme, keeping track of a large number of moving blocks imposes
significant overhead. This penalty is paid most in trying to allocate non-relocatable
(pointer) blocks. To maintain efficient memory use, the Memory Manager always
tries to allocate a non-relocatable block toward the bottom or top of the heap, so it
won't get in the way during a memory shuffle. Finding space at the edge of the heap
while a program is running typically involves a major memory move.
The result is that Memory Manager allocation time per block is distinctly non-linear.
While handle allocation drags its tail, the time needed to allocate non-relocatable
blocks is atrocious. Chart 1 below illustrates block allocation times in ticks (a tick is
1/60th of a second).
So, the Macintosh Memory Manager optimizes memory usage, but at a significant cost
in speed, and with danger lurking in the form of dangling pointers.
OOPC Low-Level Memory Management
OOPC has its own memory manager, using pointers. This avoids the dangling pointer
problem caused by using handles. The OOPC memory manager grabs a large chunk of
memory from the heap, then slices it up for use. The first chunk is 128 KB. Other
chunks are as big as needed, but at least 32 KB. (Chunk sizes are adjustable by an
OOPC programmer). Chunks are locked at the top of the heap so they don't get in the
way of operating system memory management. Chunks are allocated and freed as
needed.
The result is pointer block allocation that does not fragment the heap. Because empty
space is managed, not allocated blocks, allocation time is linear. Chart 2 below
compares OOPC memory management times to Macintosh Memory Manager times. The
times shown are for allocation and deallocation.At 20,000 blocks, OOPC is 20 times
faster than Mac OS handles, and 100 times faster than Mac OS pointers.
Low-level memory management is implemented in OOPC using platform-independent
functions, such as get_block and free_block. Handle functions (such as get_handle and
free_handle) are also supported (using the Mac OS Memory Manager for the Mac
version of OOPC). The OS requires handles for resources and some other tasks (such as
color pixel maps).
Low-level memory management is only occasionally called directly (by an application
programmer). More often, objects are created and released. (Of course, internal
methods rely upon low-level memory management.) Let's look at how objects are
allocated and deallocated in OOPC.
OOPC Object Memory Management
OOPC object memory management is quite simple. There are six functions/methods
involved: new_object (a function), and new, bind, trash, release and empty (all
methods).
The new_object function call creates an object, allocating a small amount memory
required for all objects (36 bytes), plus any application data the object keeps.
new_object calls the new function, which dispatches to an initial data assignment
method (the method depends on the class of the object being created). After the new
method finishes, an object is fully allocated and initialized.
Some objects need to keep track of other objects. A document object, for example, owns
the window object that displays the document. A document object also keeps track of the
pages in the document. These links between objects must be maintained.
OOPC's object system has built-in support for maintaining object links. Links between
objects are established by calling bind(thisobject, thatobject). By this call, thatobject
has created a link (or bond) to thisobject. thisobject cannot be released until
thatobject trashes the bond between the objects by calling trash(thisobject,
thatobject). Once all links to an object are trashed, the object is released by a trash
(or release) call. Using bind and trash functions provides simple garbage collection,
preventing objects from being released prematurely (and risking access to 'dead'
objects).
release releases an object, but only if there are no links to other objects. When an
object is released, empty is called (by release) to deallocate any data allocated in the
new method. The release method disposes of the object itself.
Conclusion
Having its own low-level memory management gives a significant speed boost to
OOPC-built applications and keeps programming chores simple. At the high level,
built-in support for object linking provides garbage collection and safety with object
access.