Memory Management
From DocR21
The ACIS product implements a Memory Management system, which provides support for all of its memory requirements in order to utilize memory resources effectively and efficiently. The main feature of the ACIS memory manager is its interface, through which all memory requests pass. This interface contains methods that provide a common means for allocating and deallocating memory, collecting statistics about memory usage, auditing for memory leaks, and pattern filling. This system, along with all of its functionality, can also be taken advantage of by ACIS based applications.
What Can ACIS Memory Management Do?
ACIS memory management can:
- Route all memory requests through a common interface for processing;
- Allocate and deallocate memory, and allow this to be customizable by the application developer;
- Pattern-fill unused memory with SNaN (Signal Not A Number) or other easily recognizable data to help spot references to uninitialized memory;
- Collect statistics about memory usage, such as who allocates memory, how much memory is being used, the maximum amount of memory used, and where memory leaks have occurred; and
- Allow the application developer to implement memory management strategies using a proprietary or commercial memory manager.
The ACIS memory manager is capable of capturing and servicing all types of memory requests—from the common C-runtime functions such as malloc, calloc, realloc, strdup, and free, to object allocation and destruction using class-level new and delete operators, to the use of global new and delete operators on non-class object data (refer to Capturing Memory Requests). The ACIS memory manager provides mechanisms to route all of these memory requests to the interface for processing (refer to Managing Application Memory).
The behavior of the ACIS memory manager cannot change once it has been used and must therefore be configured, if desired, before the ACIS modeler is started. The application developer can change the default behavior of the memory manager during this configuration period (refer to Initialization and Termination).
The memory manager has many capabilities that an application developer can take advantage of. The free lists, for example, provide a fast small-block allocation strategy for class objects, which significantly enhances performance (refer to Free List Mechanism). The memory auditing capabilities, used mainly in debug builds, can help track down application memory leaks (refer to Gathering Memory Auditing Information) and provide useful information about application memory consumption (refer to Gathering Cumulative Memory Statistics).
The application developer can also extend the capabilities of the memory manager by installing a simple allocator and simple destructor during the configuration period. This will cause all memory management requests, even for free list and audit blocks, to ultimately call these functions for memory and provides the lowest level of control (refer to Customizing Low Level Memory Management).
The application developer can also circumvent the capabilities provided by the delivered system, and take complete control of ACIS memory management, by installing a complex allocator and complex destructor during the runtime configuration period (refer to Customizing Low Level Memory Management).
Initialization and Termination
The memory manager is the first thing to be initialized when an application either initializes the Base component explicitly or starts the ACIS modeler. Its functionality remains active until the Base component is terminated or the modeler is stopped. Some features of the memory manager such as leak detection, however, remain active until the application terminates.
Any and all modifications to the behavior of the memory manager must be performed by initializing the memory manager during the configuration period, which is after the main function is called and before the modeler is started. The system will automatically be set to the default behavior after this period.
Configuration Choices
Beginning with ACIS Release 7, the configurable functionalities of the ACIS memory management system have been delayed until run-time. Passing an appropriately initialized base_configuration object to the initialize_base routine changes the configuration, as the following code snippet shows:
// install simple allocator and destructor // enable memory auditing and disable free lists void *my_malloc(size_t size) { return malloc(size); } void my_free(void *ptr) { free(ptr); } int main(int argc, char* argv) { base_configuration base_config; // construct config object base_config.enable_audit_leaks = TRUE; // turn on auditing base_config.enable_freelists = FALSE; // turn off free lists base_config.raw_allocator = my_malloc; // install raw allocator base_config.raw_destructor = my_free; // and destructor initialize_base( &base_config); // initialize the base unlock_products(); // add license key here api_start_modeller(); . . api_stop_modeller(); terminate_base(); return 0; }
Note: This is a unique case where an ACIS function, specifically initialize_base, is called before starting the modeler.
Note: Whenever initialize_base is called (before starting the modeler), terminate_base must be called after stopping the modeler.
If initialize_base is not called (or if it is called with the default NULL argument), then the memory manager configuration defaults are set according to whether the ACIS product is the debug or production version. These defaults (listed below) have been chosen to provide the expected and desired behavior; only users with special needs should need to explicitly change the settings. If initialize_base is called, ensure that you are calling the <unlock_products key function after this call.
The base_configuration class contains data members that allow users to specify allocation and deallocation functions, as well as flags to turn on/off auditing and free listing functionality. The following table lists the flags, their production and debug default values, and their meanings.
| Name | Production Library Default | Debug Library Default | Meaning |
|---|---|---|---|
| enable_freelists | TRUE | FALSE | No free lists if FALSE |
| enable_audit_leaks | FALSE | TRUE | Turns on memory auditing and leak detection if TRUE |
| enable_audit_logs | FALSE | FALSE | Turns on memory logging of all allocations, deletions, and ENTITY::lose() calls |
Corresponding run-time flags have been added to the Scheme AIDE Commands Component. These flags are as follows:
| ON | OFF |
|---|---|
| -auditleaks | -noauditleaks |
| -freelists | -nofreelists |
| -auditlogs |
Customizing Low Level Memory Management
The actual allocation and deallocation strategy of low level ACIS memory management can be replaced by the application developer with other commercial or custom memory management packages. The delivered product is optimized for best performance, but if required, the application developer can modify the memory manager functionality during the configuration period, which is after the main and before the modeler is started. The system will automatically be set to the default behavior after this period.
The application can use the delivered system, install a raw allocator and destructor to be used by all ACIS memory requests, or install a complex allocator and destructor, which completely bypasses ACIS memory management system and requires the application to process all memory requests.
When the ACIS memory manager is enabled, all calls to allocate and deallocate memory are ultimately routed through the interface to the low-level acis_allocate and acis_discard functions, respectively.
An application supplying runtime configuration arguments to the initialize_base function can choose between two types of control, either simple or complex. The first modifies the behavior of the delivered system by causing all memory requests to call the supplied functions for memory blocks (as in the example code above). The signatures and behavior of these functions should be identical to the C-runtime malloc and free functions.
The second completely replaces the delivered system by supplying functions, which are called directly for all memory requests. This completely circumvents the delivered system and all of its capabilities (such as free lists and auditing). All available arguments are supplied to these functions to allow the application to create like functionality. The complex allocator and destructor are installed by appropriately initializing a base_configuration object and passing it to the initialize_base routine, as the following code snippet shows:
// install a complex allocator and destructor void* my_complex_allocater(size_t alloc_size, AcisMemType alloc_type, AcisMemCall alloc_call, const char* alloc_file, int alloc_line) { return malloc( alloc_size); } void my_complex_destructor(void* alloc_ptr, AcisMemCall alloc_call, size_t alloc_size) { free(alloc_ptr); } int main(int argc, char* argv) { base_configuration base_config; // construct config object base_config.complex_allocator = // install complex allocator my_complex_allocater; base_config. complex_destructor = // and destructor my_complex_destructor; initialize_base(&base_config); // initialize the base unlock_products(); // add license key here api_start_modeller(); . . api_stop_modeller(); terminate_base(); return 0; }
Supplying runtime configuration selections to the initialize_base function is optional. The memory manager will, by default, choose the appropriate settings for a release or debug build.
Managing Application Memory
By default, ACIS memory management is used only within ACIS code. However, the application developer can also use ACIS memory management within application code.
When an application wants to use ACIS memory management instead of the C run-time functions (malloc, calloc, realloc, strdup, and free), the developer can replace these function calls with the following ACIS macros:
- ACIS_MALLOC(alloc_size),
- ACIS_CALLOC(alloc_num, alloc_size),
- ACIS_REALLOC(memblock,alloc_size),
- ACIS_STRDUP(orgstring),
- ACIS_FREE(alloc_ptr).
When a class in the application is derived from the ACIS_OBJECT base class, it inherits the ACIS_OBJECT class-level new and delete operators, and is then automatically managed by the memory manager.
When the application wants to route global new and delete calls through the ACIS memory manager, it can directly replace the new with the ACIS_NEW macro, and directly replace delete with the ACIS_DELETE macro, for example:
double* ptr = new double;
would become
double* ptr = ACIS_NEW double;
As one can see, returning unneeded memory blocks back to the system heap is accomplished with ACIS_FREE in the C-runtime cases and with class-level operator delete in the class-object cases. The global allocation of memory using new in conjunction with non-class objects, however, requires special handling to perform the delete correctly. You must use the STD_CAST macro to help route the pointer to these memory blocks back to the memory manager, for example:
// allocate a single double double* double_ptr = ACIS_NEW double; // allocate an array of 4 integers int* int_array_pointer = ACIS_NEW int[4]; . . // delete the basic memory block by routing with STD_CAST macro ACIS_DELETE STD_CAST double_ptr; // ditto, and remember to use the [] with the array delete ACIS_DELETE [] STD_CAST int_array_ptr;
It is extremely important to recognize how critical it is to use the STD_CAST macro correctly. Not using it correctly can easily lead to unexpected application behavior and/or application crashes. The consequences of not using a STD_CAST where it is needed will typically result in an erroneous memory-leak report in debug builds. However, in applications that install low-level memory functions when initializing the memory manager, the consequences could be much worse, because the memory block will be deleted with the global delete operator instead of the destructor function installed in the memory manager.
Follow this list of rules when using the memory manager in an application:
- Use the replacement C-runtime macros ACIS_MALLOC, ACIS_CALLOC, ACIS_REALLOC, ACIS_STRDUP, and ACIS_FREE;
- Derive base classes from ACIS_OBJECT;
- Use ACIS_NEW and ACIS_DELETE;
- Use STD_CAST with ACIS_DELETE on non-class objects (that is, basic types).
Capturing Memory Requests
Some compilers support overloaded new and delete operators, and can support routing all memory requests to the memory manager. Other compilers have more limited capabilities, and support routing only a subset of memory calls to the memory manager.
The compiler-supplied capabilities that are required to enable various stages of memory manager support are grouped as follows:
- Group 1 - C run-time memory functions malloc, calloc, realloc, strdup, and free are routed through the manager.
- Group 2 - Class-level new and delete operators are routed through the memory manager, as well as Group 1 memory requests. All classes derived from ACIS_OBJECT receive the basic memory operators and fall into this category. The ACIS_OBJECT::new and ACIS_OBJECT::delete operators in turn call the acis_allocate and acis_discard functions. For memory not allocated to the free lists (if enabled), the acis_allocate and acis_discard functions call the raw_allocator and raw_destructor, respectively, which can, of course, be customized by the application developer (refer to Customizing Low Level Memory Management).
- Group 3 - Class-level array new and array delete requests are routed through the memory manager, as well as Group 2 and Group 1 memory requests.
- Group 4 - Decorated version of global new and array new are routed through the memory manager, as well as Group 3, Group 2, and Group 1 memory requests. At this level of memory management, all ACIS memory requests are routed through the memory manager (100% coverage), and all of the configurable options of the memory manager are available.
Note: Each successive group represents a more comprehensive level of memory management, and each group encompasses both itself and lower level groups. For example, memory management functionality in Group 3 also includes the functionality in Group 2 and in Group 1.
Gathering Cumulative Memory Statistics
The run-time memory usage statistics are automatically gathered by the debug version of the delivered system or with the auditing runtime configuration argument set to TRUE. The data is collected into the following structure and can be retrieved with a call to mmgr_debug_stats(), as the following example shows.
// memory manager statistics structure struct mmgr_statistics { size_t high_bytes; // the high water mark size_t alloc_bytes; // total bytes allocated size_t alloc_calls; // number of allocation calls size_t free_bytes; // total bytes freed size_t free_calls; // number of free calls size_t size_array[257]; // most frequent allocations sizes size_t double_deletes; // non-audited address delete count size_t mismatched_callers; // array allocation - // non array delete for example size_t mismatched_sizes; // allocated as foo - deleted as bar };
// retrieve the current memory manager statistics struct mmgr_statistics* my_stats = mmgr_debug_stats();
Gathering Memory Auditing (Leak) Information
The memory manager gathers information on memory leaks by default in the debug version of ACIS, or when the audit_leaks flag is set to TRUE when initializing the base component. The memory manager attempts to pair each allocation with a corresponding deallocation during runtime, and reports all unpaired allocations (memory leaks) during termination.
The statistics gathered for each leak include:
- alloc_num - a running count of the number of allocations made
- alloc_size - the size in bytes of the allocation made
- alloc_type - the type of allocation made
- alloc_call - the allocation call made
- alloc_line - the source code line number where the call was made, in DEBUG builds
- alloc_file - the source file name from which the call was made, in DEBUG builds
Enabling and Disabling Memory Manager Log
A detailed log file called mmgr.log containing the statistics and leak information is optionally generated at application exit. The filename can be changed using the mmgrfile option. Setting the mmgrlog option to FALSE will disable the log dump altogether. A debug build will also contain file and line information, whereas release builds will typically not. The log file (mmgr.log) can also be disabled by setting the enable_audit_leaks variable to FALSE during the call to the initialize_base function. Conversely the mmgr.log file can be enabled by setting the variable to TRUE. However, it should be noted that the call to the initialize_base function must happen before starting the modeler, as shown in the first example above.
Understanding the Contents of the Memory Manager Log
The log file contains two types of information:
- Statistical information
- Memory leak information
Example Statistical Block
'*** Statistics ***' Leaks: 276352 Bytes 137770 allocations for 10396374 Bytes 131817 deletes for 10120022 Bytes High-water-mark: 3022316 Bytes Bad delete pointers: 0 Mismatched caller pointers: 23081 Stack high-water-mark: 333948 Bytes Elapsed seconds: 16.315
The first few lines report cumulative statistics of the number of calls and amount of memory leaked, allocated, and deleted. The high-water-mark reports the largest amount of heap memory used by the modeler at any one time. The bad delete pointers report the number of unknown pointers deallocated by the memory manager. These are typically double deletes and may indicate severe problems. The mismatched caller pointers report the number of allocate/delete sequences that do not match. These are typically harmless but are relatively easy to correct. An example of a mismatch is allocating a block of memory with malloc and freeing it with operator delete (instead of free). More details are discussed below in Understanding Call Matching. The stack high water mark reports the largest amount of stack memory used by the modeler at any one time.
Example Memory Leak
c:\acis\spakern\kernel_spline_agspline_bs3_crv.m\src\c3curve.cpp(119) : {0000270302} at 0x093932F0 16 Bytes Type: 1 Call: 27
Each line contains seven pieces of information. The first two are the file and line where the allocation took place. This is followed by the allocation sequence number, the address of the storage location, and the number of bytes allocated. Next comes the call type, which is a feature that has only been used for internal testing purposes. The final entry is the allocation type, which specifies the routine used to allocate the memory block.
Understanding Call Matching
The memory manager tracks the routines used to allocate and free memory. With this information, it can detect when inconsistencies occur. These are reported as mismatches. A rather lengthy list of call types are tracked, as can be seen by the AcisMemCall enumeration in mmgr.hxx.
The ones relevant to customers are as follows:
| enumeration | routine | value | match | routine |
|---|---|---|---|---|
| eMalloc | ACIS_MALLOC | 2 | eFree | ACIS_FREE |
| eCalloc | ACIS_CALLOC | 3 | eFree | ACIS_FREE |
| eRealloc | ACIS_SAFE_REALLOC | 4 | eFree | ACIS_FREE |
| eStrdup | ACIS_STRDUP | 5 | eFree | ACIS_FREE |
| eGlob | new | 7 | eGlob | delete |
| eGlobDeco | ACIS_NEW | 8 | eClassStd | ACIS_DELETE STD_CAST |
| eGlobArray | new [] | 9 | eGlobArray | delete [] |
| eGlobDecoArray | ACIS_NEW [] | 10 | eClassArrayStd | ACIS_DELETE [] STD_CAST |
| eClass | new class object | 13 | eClass | delete class object |
| eClassDeco | ACIS_NEW class object | 14 | eClassDeco | ACIS_DELETE class object |
| eClassArray | new [] class object | 15 | eClassArray | delete [] class object |
| eClassDecoArray | ACIS_NEW [] class object | 16 | eClassDecoArray | ACIS_DELETE [] class object |
| eClassSize | new ACIS_OBJECT | 19 | eClassSize | delete ACIS_OBJECT |
| eClassSizeDeco | ACIS_NEW ACIS_OBJECT | 20 | eClassSize | ACIS_DELETE ACIS_OBJECT |
| eClassSizeArray | new [] ACIS_OBJECT | 21 | eClassSizeArray | delete [] ACIS_OBJECT |
| eClassSizeDecoArray | ACIS_NEW [] ACIS_OBJECT | 22 | eClassSizeArray | ACIS_DELETE [] ACIS_OBJECT |
| eNewFreelist | used internally for b-splines | 27 | eDelFreelist | used internally for b-splines |
"Breaking" on a Particular Allocation Number
ACIS has a sophisticated memory management system that you can customize. One such powerful feature is "memory auditing", in which ACIS tracks its own allocations as well as any ACIS-specific allocation calls such as ACIS_NEW and ACIS_MALLOC. When using the memory auditing capabilities, memory leaks are reported in an mmgr.log file. As an example, a leak might be reported as:
c:\acis\spakern\kernel_spline_agspline_bs3_crv.m\src\c3curve.cpp(119) : {0000270302} at 0x093932F0 16 Bytes Type: 1 Call: 27
Allocations are counted; the allocation number for this leak is 270302. That is to say, the 270302nd allocation leaked.
It is possible to "break" into the debugger when this allocation takes place. This can be useful in determining how to clean up the leak. In order to do this, your application must replace the "raw" allocator and destructor used by ACIS. This can be done by first implementing the following two functions:
void* break_alloc(size_t s) { mmgr_statistics* stats = mmgr_debug_stats(); // To stop at a given allocation number put a break on // the line "a = 1" below. When it stops, set a to the allocation // number, reset your breakpoint to the "a = a" line and keep going. static size_t a = 1; if (a == stats->alloc_calls) { a = a; } // ANSI allocation rules - even if asking for 0 bytes, a valid // ptr must be returned. if (s == 0) s = 8; return malloc(s); } void break_free(void* p) { free(p); }
Now, call initialize_base (prior to calling api_start_modeller), as follows:
base_configuration base_config; base_config.enable_audit_leaks = TRUE; base_config.enable_freelists = FALSE; base_config.raw_allocator = break_alloc; base_config.raw_destructor = break_free; logical ok = initialize_base(&base_config);
In this configuration, the lowest-level allocation function in ACIS has been replaced with your own function break_alloc. By doing this, you can track every allocation, so long as the need for memory in ACIS is satisfied, hence the need to call malloc. To capture the allocation, simply follow the instructions in the comment:
// To stop at a given allocation number put a break on // the line "a = 1" below. When it stops, set a to the allocation // number, reset your breakpoint to the "a = a" line and keep going.
Pattern Filling Unused Memory
The memory manager pattern-fills memory, when memory auditing is enabled, directly after allocation and deallocation with strategic values that can help identify typical memory usage errors such as
- accessing uninitialized memory,
- accessing deleted memory, and
- double-deleting memory.
The bit pattern used is subject to change, but will be seen as an SNAN in the FPU and as an implausibly large value in other cases.
Free List Mechanism
Beginning with ACIS Release 5.3, ACIS memory management uses the capabilities of the interface to provide a fast allocation mechanism we call the free lists. By using interface data to determine optimum strategy, the memory manager can speed up allocations significantly. Instead of satisfying specific allocation requests from the system heap, the memory manager makes instance-by-instance decisions on the use of the free list mechanism. Objects derived from ACIS_OBJECT automatically use the free list capabilities if possible.
The free list system doles out pointers to specific sizes of memory from internally managed data blocks. When a new block of memory is needed, it is allocated from the system and added to the appropriate free list. ACIS free list objects allocate 4KB blocks of memory, parceled into slots of 16, 32, 48, 64, 96, or 128 bytes respectively, as needed. Based on analysis of typical ACIS usage, these six sizes have been determined to be most effective.
When the program frees the memory, the corresponding slot within the memory block is marked as unused. When all slots within a given block are marked as unused, the block can either be returned to the operating system or kept and reused. The application has three choices in dealing with unused memory blocks:
- Unused blocks can be retained indefinitely (default). This leads to very fast execution by minimizing the number of operating system allocation requests, but creates a memory footprint that grows without shrinking until program termination. This behavior is enabled by calling the function keep_all_free_lists.
- Unused blocks can be automatically returned to the operating system. This yields a small and dynamic memory footprint for ACIS, but may result in more (slower) allocations from the operating system as memory is requested. This behavior is enabled by calling the function collapse_all_free_lists.
- The application can return unused blocks to the operating system when it chooses. In this case, unused blocks are retained and reused until the application specifically collapses the free lists to return unused memory to the operating system. For example, the application could keep all unused blocks while a model is read in and worked on, thus speeding execution, and then, when done with that model, collapse the free lists and return all now unused memory to the operating system. This can be accomplished by calling the function clear_all_free_lists.
Platform Defaults
ACIS is shipped with the maximum level of memory management functionality available on the platform. At run-time, you can modify the level of memory management used.
The release libraries are fully optimized. The release version will default to enable the free lists provided by the delivered system and will default to disable the auditing capabilities. The test applications bundled with our standard releases utilize the ACIS components as shipped.
Note: The ability of the memory manager to capture all memory requests varies by platform according to the capabilities of individual compilers.
See Also
- Allocating and Deallocating Memory within ACIS
- Minimizing Memory
- Geometry Caching and the Global Cache Manager
- ACIS Progress Metering
- Versioning within ACIS
- Understanding False Memory Leak Reported in ACIS-based MFC Applications
