Skip to content

Commit 5601448

Browse files
authored
Restructure and refactor the memory pool implementation. (#10901)
Restructure and refactor the memory pool implementation. - The memory pool is organized as a linked list of blocks of memory. - The initial block has 2MB size right now. - Each block is, at least, 1.5x the old block size aligned to the initial block size (2MB right now). It might be bigger if the request that caused the new block allocation is actually bigger than 1.5x the old block size. - Error is reported when a request is made for a new block of size 1GB or more. This is clearly a misfunction and should be caught and reported before we get to 4GB requests and risk overflowing the values that keep track of status. - The current state of the memory pool can be saved in the new struct `MemPoolState`. This will save the current block and how much of it used at the moment. - Restoring a state will cleanup all blocks created after the state was saved and then restores the used amount tracker to the value saved in the state. Save an restore the memory pool state in FMU operations. - Save and restore the memory pool state at some relevant locations in the lifecycle of an FMU. - Note that, this is not exhaustive coverage. It is just added in places where it seemed appropriate. More places can be covered if needed to avoid more wasting of reusable memory.
1 parent 3e1a0ac commit 5601448

File tree

3 files changed

+159
-73
lines changed

3 files changed

+159
-73
lines changed

OMCompiler/SimulationRuntime/c/gc/memory_pool.c

Lines changed: 119 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -43,115 +43,165 @@
4343
extern "C" {
4444
#endif
4545

46-
static int GC_collect_a_little_or_not(void)
47-
{
48-
return 0;
49-
}
50-
51-
typedef struct list_s {
52-
void *memory;
53-
size_t used;
54-
size_t size;
55-
struct list_s *next;
56-
} list;
46+
#define OMC_MEGABYTE 1024*1024
47+
/// 2MB pool by default
48+
#define OMC_INITIAL_BLOCK_SIZE 2*OMC_MEGABYTE
49+
/// Error out at a 1GB block request. Something is clearly not going
50+
/// as intended. If we continue we will most probably overflow at the
51+
/// 4GB mark anyway so catch it and report it.
52+
#define OMC_ERROR_AT_EXPAND_REQUEST 1024*OMC_MEGABYTE
53+
54+
55+
/// This is the pointer to the current block of memory. The 'memory pool'.
56+
/// It changes when the program requests memory space that does not fit
57+
/// in the current block. In which case a new block will be created and
58+
/// this will be updated. Restoring a saved state (a cleanup operation)
59+
/// will also update this.
60+
OMCMemPoolBlock *memory_pools = NULL;
5761

5862
#if !defined(OMC_NO_THREADS)
5963
static pthread_mutex_t memory_pool_mutex = PTHREAD_MUTEX_INITIALIZER;
6064
#endif
61-
static list *memory_pools = NULL;
6265

63-
static void pool_init(void)
66+
static int GC_collect_a_little_or_not(void)
6467
{
65-
memory_pools = (list*) omc_alloc_interface.malloc_uncollectable(sizeof(list));
66-
memory_pools->used = 0;
67-
memory_pools->size = 2*1024*1024; /* 2MB pool by default */
68-
memory_pools->memory = omc_alloc_interface.malloc_uncollectable(memory_pools->size);
69-
memory_pools->next = NULL;
68+
return 0;
7069
}
7170

72-
static size_t upper_power_of_two(size_t v)
71+
static void pool_init(void)
7372
{
74-
v--;
75-
v |= v >> 1;
76-
v |= v >> 2;
77-
v |= v >> 4;
78-
v |= v >> 8;
79-
v |= v >> 16;
80-
v++;
81-
return v;
73+
memory_pools = (OMCMemPoolBlock*) omc_alloc_interface.malloc_uncollectable(sizeof(OMCMemPoolBlock));
74+
memory_pools->used = 0;
75+
memory_pools->size = OMC_INITIAL_BLOCK_SIZE;
76+
memory_pools->memory = omc_alloc_interface.malloc_uncollectable(memory_pools->size);
77+
memory_pools->previous = NULL;
8278
}
8379

8480
static inline size_t round_up(size_t num, size_t factor)
8581
{
86-
return num + factor - 1 - (num - 1) % factor;
82+
return num + factor - 1 - ((num + factor - 1) % factor);
8783
}
8884

8985
static inline void pool_expand(size_t len)
9086
{
91-
list *newlist = NULL;
92-
if (0==memory_pools) {
93-
pool_init();
94-
}
95-
/* Check if we have enough memory already */
96-
if (memory_pools->size - memory_pools->used >= len) {
97-
return;
87+
OMCMemPoolBlock *newBlock = NULL;
88+
89+
// The new block will be 1.5x the current block's size. More if we request a very large array.
90+
size_t new_size = 3*memory_pools->size / 2;
91+
// Align the new size to the initial block size (2MB right now) for easier debugging.
92+
new_size = round_up(new_size, OMC_INITIAL_BLOCK_SIZE);
93+
94+
// Report an error if the size is too big. This will error out before the size request is
95+
// able to overflow the the size_t size (at 4GB)
96+
if (new_size >= OMC_ERROR_AT_EXPAND_REQUEST) {
97+
omc_assert_macro(0 && "Attempt to allocate an unusually large memory. The memory management does not seem to be working as intended. Please create an issue on https://github.com/OpenModelica/OpenModelica/issues.");
9898
}
99-
newlist = (list*) omc_alloc_interface.malloc_uncollectable(sizeof(list));
100-
newlist->next = memory_pools;
101-
memory_pools = newlist;
102-
memory_pools->used = 0;
103-
memory_pools->size = upper_power_of_two(3*memory_pools->next->size/2 + len); /* expand by 1.5x the old memory pool. More if we request a very large array. */
104-
memory_pools->memory = omc_alloc_interface.malloc_uncollectable(memory_pools->size);
99+
100+
newBlock = (OMCMemPoolBlock*) omc_alloc_interface.malloc_uncollectable(sizeof(OMCMemPoolBlock));
101+
newBlock->used = 0;
102+
newBlock->size = new_size;
103+
newBlock->memory = omc_alloc_interface.malloc_uncollectable(newBlock->size);
104+
newBlock->previous = memory_pools;
105+
memory_pools = newBlock;
105106
}
106107

107-
static void* pool_malloc(size_t sz)
108+
static void* pool_malloc(size_t requested_size)
108109
{
109110
void *res;
110-
sz = round_up(sz,8);
111+
requested_size = round_up(requested_size, 8);
112+
111113
#if !defined(OMC_NO_THREADS)
112114
pthread_mutex_lock(&memory_pool_mutex);
113115
#endif
114-
pool_expand(sz);
116+
117+
/// If we forgot to explicitly initialize the pool, initialize it now.
118+
if (!memory_pools) {
119+
pool_init();
120+
}
121+
122+
/// If the current block does not have enough remaining space, expand the pool
123+
/// by creating another block. The new block should, at least, be as big as
124+
/// the requested size. Note that, this will update the global memory_pools pointer.
125+
if (memory_pools->size - memory_pools->used < requested_size) {
126+
pool_expand(requested_size);
127+
}
128+
115129
res = (void*)((char*)memory_pools->memory + memory_pools->used);
116-
memory_pools->used += sz;
130+
memory_pools->used += requested_size;
131+
117132
#if !defined(OMC_NO_THREADS)
118133
pthread_mutex_unlock(&memory_pool_mutex);
119134
#endif
120-
memset(res,0,sz);
135+
136+
memset(res, 0, requested_size);
121137
return res;
122138
}
123139

124-
static int pool_free_extra_list(void)
140+
static int pool_collect_a_little()
125141
{
126-
list* current = memory_pools;
127-
if (NULL == current) {
128-
return 0;
129-
}
142+
return 0;
143+
}
144+
145+
static void print_mem_pool(OMCMemPoolBlock* chunk) {
146+
printf("----------------------------\n");
147+
printf("%p, %ld, %ld, %p\n", chunk->memory, chunk->used, chunk->size, chunk->previous);
148+
printf("----------------------------\n");
149+
}
150+
151+
MemPoolState omc_util_get_pool_state() {
152+
MemPoolState state;
153+
state.block = memory_pools;
154+
state.used = memory_pools->used;
130155

131-
// Delete all chunks except the last one.
132-
while (current->next) {
133-
list *next = current->next;
134-
omc_alloc_interface.free_uncollectable(current->memory);
135-
omc_alloc_interface.free_uncollectable(current);
136-
current->next = NULL;
137-
current->size = 0;
138-
current->used = 0;
139-
current = next;
156+
return state;
157+
}
158+
159+
void omc_util_restore_pool_state(MemPoolState in_state) {
160+
// printf("original state:\n");
161+
// print_mem_pool(memory_pools);
162+
163+
assert(in_state.block);
164+
165+
OMCMemPoolBlock* currentBlock = memory_pools;
166+
/// Start from the current block and traverse the chain until we find the block
167+
/// that was saved in the state.
168+
/// Clean up the blocks as we go since they will no longer be reachable after updating
169+
/// to the saved state.
170+
while (currentBlock != in_state.block) {
171+
OMCMemPoolBlock* previous = currentBlock->previous;
172+
omc_alloc_interface.free_uncollectable(currentBlock->memory);
173+
currentBlock->memory = NULL;
174+
currentBlock->previous = NULL;
175+
currentBlock->size = 0;
176+
currentBlock->used = 0;
177+
omc_alloc_interface.free_uncollectable(currentBlock);
178+
currentBlock = previous;
140179
}
180+
assert(currentBlock);
141181

142-
memory_pools = current;
182+
currentBlock->used = in_state.used;
183+
memory_pools = currentBlock;
143184

144-
return 0;
185+
// printf("updated state:\n");
186+
// print_mem_pool(memory_pools);
145187
}
146188

147189
void free_memory_pool()
148190
{
149-
pool_free_extra_list();
150-
if (memory_pools) {
151-
omc_alloc_interface.free_uncollectable(memory_pools->memory);
152-
omc_alloc_interface.free_uncollectable(memory_pools);
153-
memory_pools = NULL;
191+
OMCMemPoolBlock* currentBlock = memory_pools;
192+
193+
while (currentBlock) {
194+
OMCMemPoolBlock* previous = currentBlock->previous;
195+
omc_alloc_interface.free_uncollectable(currentBlock->memory);
196+
currentBlock->memory = NULL;
197+
currentBlock->previous = NULL;
198+
currentBlock->size = 0;
199+
currentBlock->used = 0;
200+
omc_alloc_interface.free_uncollectable(currentBlock);
201+
currentBlock = previous;
154202
}
203+
204+
memory_pools = NULL;
155205
}
156206

157207
static void nofree(void* ptr)
@@ -179,7 +229,7 @@ omc_alloc_interface_t omc_alloc_interface_pooled = {
179229
pool_malloc,
180230
(char*(*)(size_t)) malloc,
181231
strdup,
182-
pool_free_extra_list,
232+
pool_collect_a_little, /* No OP. Does not do anything. The pool requires explicit state save and restore. */
183233
malloc_zero,
184234
free,
185235
malloc,
@@ -246,7 +296,7 @@ omc_alloc_interface_t omc_alloc_interface = {
246296
pool_malloc,
247297
(char*(*)(size_t)) malloc,
248298
strdup,
249-
pool_free_extra_list,
299+
pool_collect_a_little, /* No OP. Does not do anything. The pool requires explicit state save and restore. */
250300
malloc_zero /* calloc, but with malloc interface */,
251301
free,
252302
malloc,

OMCompiler/SimulationRuntime/c/gc/memory_pool.h

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,35 @@
3838
extern "C" {
3939
#endif
4040

41+
/// @brief
42+
/// The memory pool is a linked list of blocks of memory. Each block has its own
43+
/// chink of memory space to be used for requests by the program. It knows the size
44+
/// of the chunk and keeps track of how much of it used currently. Each block also has
45+
/// a pointer to the previous block.
46+
typedef struct OMCMemPoolBlock_s {
47+
void *memory;
48+
size_t used;
49+
size_t size;
50+
struct OMCMemPoolBlock_s *previous;
51+
} OMCMemPoolBlock;
52+
53+
/// @brief
54+
/// The current state of the pool can be represented by a pointer to the current block
55+
/// and the currently used amount of that block. Restoring will reset the pool pointer
56+
/// the block saved in the state and then restors the used value when the satate was created.
57+
typedef struct {
58+
OMCMemPoolBlock *block;
59+
size_t used;
60+
} MemPoolState;
61+
62+
/// @brief Get the current state of the pool (the current block and used amount in that block)
63+
MemPoolState omc_util_get_pool_state();
64+
/// @brief Restors the memory pool to a given state (specifc block and used amount in that block).
65+
void omc_util_restore_pool_state(MemPoolState in_state_v);
66+
/// @brief Completely cleans up the memory pool by deleting all blocks.
67+
void free_memory_pool();
68+
69+
4170
/* Allocation functions */
4271
extern modelica_real* real_alloc(int n);
4372
extern modelica_integer* integer_alloc(int n);
@@ -48,8 +77,6 @@ extern _index_t** index_alloc(int n);
4877

4978
void* generic_alloc(int n, size_t sze);
5079

51-
void free_memory_pool();
52-
5380
#if defined(__cplusplus)
5481
} /* end extern "C" */
5582
#endif

OMCompiler/SimulationRuntime/fmi/export/openmodelica/fmu2_model_interface.c.inc

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -244,8 +244,6 @@ static inline void resetThreadData(ModelInstance* comp)
244244
if (comp->threadDataParent) {
245245
pthread_setspecific(mmc_thread_data_key, comp->threadDataParent);
246246
}
247-
/* Clear the extra memory pools */
248-
omc_alloc_interface.collect_a_little();
249247
}
250248

251249
static inline void setThreadData(ModelInstance* comp)
@@ -431,6 +429,7 @@ fmi2Status updateIfNeeded(ModelInstance *comp, const char *func)
431429
if (comp->_need_update)
432430
{
433431
setThreadData(comp);
432+
MemPoolState mem_pool_state = omc_util_get_pool_state();
434433

435434
/* TRY */
436435
#if !defined(OMC_EMCC)
@@ -461,6 +460,7 @@ fmi2Status updateIfNeeded(ModelInstance *comp, const char *func)
461460
threadData->mmc_jumper = old_jmp;
462461
#endif
463462

463+
omc_util_restore_pool_state(mem_pool_state);
464464
resetThreadData(comp);
465465
if (!success)
466466
{
@@ -1822,6 +1822,8 @@ fmi2Status internal_CompletedIntegratorStep(fmi2Component c, fmi2Boolean noSetFM
18221822
FILTERED_LOG(comp, fmi2OK, LOG_FMI2_CALL, "fmi2CompletedIntegratorStep")
18231823

18241824
setThreadData(comp);
1825+
MemPoolState mem_pool_state = omc_util_get_pool_state();
1826+
18251827
/* try */
18261828
MMC_TRY_INTERNAL(simulationJumpBuffer)
18271829
threadData->mmc_jumper = threadData->simulationJumpBuffer;
@@ -1851,6 +1853,7 @@ fmi2Status internal_CompletedIntegratorStep(fmi2Component c, fmi2Boolean noSetFM
18511853
MMC_CATCH_INTERNAL(simulationJumpBuffer)
18521854
threadData->mmc_jumper = old_jmp;
18531855
resetThreadData(comp);
1856+
omc_util_restore_pool_state(mem_pool_state);
18541857

18551858
if (done) {
18561859
return fmi2OK;
@@ -1924,6 +1927,7 @@ fmi2Status internalGetDerivatives(fmi2Component c, fmi2Real derivatives[], size_
19241927
return fmi2Error;
19251928

19261929
setThreadData(comp);
1930+
MemPoolState mem_pool_state = omc_util_get_pool_state();
19271931
/* try */
19281932
MMC_TRY_INTERNAL(simulationJumpBuffer)
19291933

@@ -1945,6 +1949,8 @@ fmi2Status internalGetDerivatives(fmi2Component c, fmi2Real derivatives[], size_
19451949
done=1;
19461950
/* catch */
19471951
MMC_CATCH_INTERNAL(simulationJumpBuffer)
1952+
1953+
omc_util_restore_pool_state(mem_pool_state);
19481954
resetThreadData(comp);
19491955

19501956
if (done) {
@@ -1972,6 +1978,7 @@ fmi2Status internalGetEventIndicators(fmi2Component c, fmi2Real eventIndicators[
19721978
return fmi2Error;
19731979

19741980
setThreadData(comp);
1981+
MemPoolState mem_pool_state = omc_util_get_pool_state();
19751982
/* try */
19761983
MMC_TRY_INTERNAL(simulationJumpBuffer)
19771984

@@ -1992,7 +1999,9 @@ fmi2Status internalGetEventIndicators(fmi2Component c, fmi2Real eventIndicators[
19921999

19932000
/* catch */
19942001
MMC_CATCH_INTERNAL(simulationJumpBuffer)
2002+
omc_util_restore_pool_state(mem_pool_state);
19952003
resetThreadData(comp);
2004+
19962005
if (done) {
19972006
return fmi2OK;
19982007
}

0 commit comments

Comments
 (0)