Memory Management
The Base Component (BASE), contains the code for low-level common functionality that is used by all ACIS components. BASE includes support for error management and memory management (discussed in this section).
Although ACIS uses the Base component, non-ACIS products may include the Base Component alone instead of all of the kernel to reduce its size. It can compile independently.
Topics include:
- Overview
- Initialization and Termination
- Configuration Choices
- Memory Minimization
- Customizing Low Level Memory Management
- Managing Application Memory
- Capturing Memory Requests
- Gathering Cumulative Memory Statistics
- Gathering Memory Auditing (Leak) Information
- "Breaking" on a Particular Allocation Number
- Enabling and Disabling Memory Manager Log
- Pattern Filling Unused Memory
- Free List Mechanism
- Platform Defaults
Additional topics include:
Overview
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.
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.
- 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 (see Capturing Memory Requests). The ACIS memory manager provides mechanisms to route all of these memory requests to the interface for processing (see 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 (see Initialization and Termination).
The memory manager has many capabilities that an application developer can take advantage of. The freelists, for example, provide a fast small block allocation strategy for class objects, which significantly enhances performance (see Freelist Mechanism). The memory auditing capabilities, used mainly in debug builds, can help track down application memory leaks (see Gathering Memory Auditing Information) and provide useful information about application memory consumption (see 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 freelist and audit blocks, to ultimately call these functions for memory and provides the lowest level of control (see 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 (see 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.0, 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 freelists 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 freelists base_config.raw_allocator = my_malloc; // install raw allocator base_config.raw_desturctor = my_free; // and destructor initialize_base( &base_config); // initialize the base 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.
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.
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 freelisting 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 freelists 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 Table. Configuration Defaults
Corresponding run-time flags have been added to the Scheme AIDE Commands Component. These flags are as follows:
-auditleaks -noauditleaks -freelists -nofreelists -auditlogs Memory Minimization
Recent efforts to better understand the memory requirements of ACIS models has shown that non-analytic geometry (that is, free-form surfaces and curves) contribute the most to the size of ACIS models. This coupled with the fact that models are becoming more complex and their sizes are ever increasing, has led to the development of functionality to help reduce the model memory footprint to a minimum. This reduction in memory use can help applications perform operations successfully; resources are not completely depleted and thus, operations may complete.
The memory reduction is accomplished either by moving the geometry of a model from memory to disk (that is, paging) or by deleting geometry that can be automatically regenerated when needed (that is, lazy geometry). The memory minimization technique, because it is implemented in the core of the ACIS product, has several advantages when compared to the Operating System (OS) paging capabilities. One advantage is that you can be selective as to what is to be paged or deleted. For example, you may minimize all the geometry of an inactive model, which would obviously have the least impact on the end-user. Another advantage is that memory can actually be returned back to freestore, which is critical when virtual address space is exhausted.
ACIS memory minimization does not replace OS paging, because it serves a different purpose. OS paging is vital to application performance under normal circumstances, whereas memory minimization is useful when out-of-memory conditions occur because of virtual address space restrictions. Memory minimization, in other words, should be considered when operations may fail because their memory requirements cannot be met.
Selecting what to minimize is important to the successful use of the memory minimization capabilities. Minimizing the active part does not make sense because the geometry is most likely immediately needed by typical modeling operations. Minimizing inactive or least-used parts, on the other hand, is recommended. Also advantageous to application performance is monitoring memory use and selectively minimizing models when a high water mark is reached. This can help avoid out-of-memory situations.
Interface
The interface created to support memory minimization consists of two parts: an API and a host of direct interface functions that support the use of the API, which include page system initialization, query, and termination functions. During initialization you can influence the name and location of the physical page file with the initialize_page_system function. The effectiveness of the paging system can be evaluated through the paging statistics returned by the get_page_statistics function. The page system can be terminated with the terminate_page_system function.
The API api_minimize_entities is used to reduce the memory consumption of the input entities through various methods specified by the minimize_options object. The options specify whether to page geometry and whether lazy approximations should be deleted instead of being paged. The latter option may be preferred over paging when the geometry is less likely to be needed again.
The geometry to be processed in the minimization algorithm can be chosen by specifying appropriate input entities. The working set contains all geometry that can be reached by traversing the topological hierarchy of the input entities downward. When the input is a BODY entity, for example, all geometry is included. However, supplying a FACE entity only includes the geometry that is reachable below the face.
This functionality allows the you to reduce ACIS memory consumption when and to what extent desired. In other words,you must determine when memory minimization is beneficial to your application and must call the API accordingly.
Example Code and Scheme Extensions
The supporting example code and scheme extensions demonstrate general approaches for providing acceptable levels of memory minimization intelligence and reduce the need for end-user input. These examples demonstrate how to automatically determine "what and when" to minimize based on specific criteria.
The "what to minimize" criteria may include:
- all entities located outside of a boxed region (minimize:box)
- all entities not within the active part (minimize:inactive)
- the least accessed entities (minimize:least-used)
The "when to minimize" criteria may include:
- when memory consumption reaches some threshold (minimize:strategy ‘memory)
- when an out-of-memory error condition occurs
- when an entity has not been accessed for a period of time (minimize:strategy ‘time)
- when the active part changes (minimize:strategy ‘inactive)
- when a custom callback, which may be monitoring memory health at an application level, returns true.
The scheme commands (page:init), (page:term), and (page:stats) provide low level control of the page system. The specific action taken during minimization can be specified using the (minimize:set-defaults) and (minimize-options:set) commands. Most of the example code regarding memory minimization can be found in the file page_scm.cpp.
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 freelists 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 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. One 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. In applications, however, that install low level memory functions when initializing the memory manager, the consequences could be much worse since the memory block will get deleted with the global delete operator instead of the destructor function installed in the memory manager.
Here is a list of rules to follow 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 (i.e. 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 freelists (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 (see 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
"Breaking" on a Particular Allocation Number
ACIS has a sophisticated memory management system that can be customized by the customers. One such powerful feature is the "memory auditing" capabilities, in which ACIS tracks it's own allocations as well as any ACIS specific allocation calls like ACIS_NEW, ACIS_MALLOC, etc. When using the memory auditing capabilities, memory leaks are reported in a "mmgr.log" file. 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 so, 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.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 where 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 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 intitialize_base function must happen before starting the modeler as shown in the first example above.
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 unitialized memory
- Accessing deleted memory
- 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 freelists. 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 freelist mechanism. Objects derived from ACIS_OBJECT will automatically use the freelist capabilities if possible.
The freelist 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 freelist. ACIS freelist 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 freelists 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 freelists 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, customers can modify the level of memory management used.
The release libraries are fully optimized. The release version will default to enable the freelists 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.
[Top]
© 1989-2007 Spatial Corp., a Dassault Systèmes company. All rights reserved.