Hi all!
I am going to add an additional feature to the chunks core and I wanted
your feedback in order to chose what would benefit everyone. In my case
I will need to allocate a series of consecutive chunks for optimization
reasons (fewer memory registrations) and since this is not currently
supported I have two solutions planned for implementation :
(1) Shall I introduce a high-level function: nn_chunk_alloc_many( size_t
size, int type, int count, void*** chunks ) that allocates a number of
consecutive chunks using the allocation type specified?
Example:
void * chunks[4];
nn_chunk_alloc_many( 1024, NN_ALLOC_PAGEALIGN, 4, &chunks );
// .. use them as chunks ..
// Free them
nn_chunk_free( chunks[0] );
nn_chunk_free( chunks[1] );
nn_chunk_free( chunks[2] );
nn_chunk_free( chunks[3] );
Pros:
* Very simple and straightforward API from user's PoV
Cons:
* This requires additional reference tracking and custom de-allocator
functions in order to wait for all chunks to be free'd before the
actual memory region is released, but that's easily managed.
* In order to implement the memory registration I will need to know
the buffer base address and overall size (that in case of memory
alignment won't be equal to size * count), therefore introducing a
kind of ugly optional 5h parameter ( struct nn_chunk_meta * meta )
that will be used to track such information.
* If more fine-grained control is required it's difficult to access
the implementation internals without hacking it (btw, this is
something that I have been fighting with until I decided to actually
touch the chunk code myself).
(2) Or shall I introduce a lower-level function : nn_chunk_init( void *
ptr, size_t ptr_size, nn_chunk_free_fn destructor, void * userptr, void
** chunk ) that initializes a chunk structure to a given buffer? In this
case the user should allocate the consecutive buffer and then call this
function to initialize parts of it as chunks.
Example:
size_t chunk_size = 1024 + nn_chunk_hdrsize();
void * memory = aligned_alloc( sysconf(_SC_PAGESIZE), chunk_size * 4 );
// Create chunks
void * chunks[4];
void * ptr = memory;
for (int i=0; i<4; i++) {
nn_chunk_init( ptr, chunk_size, &free_fn, NULL, &chunks[i] );
ptr = ((uint8_t*)ptr) + chunk_size;
}
// .. use them ..
// Free chunks (calls the given free function, doesn't free anything)
nn_chunk_free( chunks[0] );
nn_chunk_free( chunks[1] );
nn_chunk_free( chunks[2] );
nn_chunk_free( chunks[3] );
// User needs to free memory eventually, or needs to
// implement the high-level logic mentioned before to
// free memory when last chunk is freed
free( memory );
Pros:
* No need to implement the reference tracking, which keeps the chunk
core cleaner
* The custom free function can be used to track
implementation-specific logic (ex. mark buffer as free for re-use)
* No need to track the individual chunks when it's time for clean-up,
just free the allocated memory.
Cons:
* The user needs to do the memory management.
* Very similar API to nn_chunk_alloc_ptr that might introduce
confusion. The difference is that the latter just creates a const
pointer-chunk to the user data, while the former assumes the data
given is a chunk and initializes it as such (writes a chunk header
and returns a pointer to the chunk data).
(3) Or shall I implement both solutions?
Looking forward to your comment/choice!
Cheers,
Ioannis