JacksonAllan / CC

A small, usability-oriented generic container library.
MIT License
188 stars 8 forks source link

Memory usage for each instance #3

Open rmolinari opened 1 year ago

rmolinari commented 1 year ago

Thanks so much for this fascinating code!

I am suggesting a function that looks like this:

size_t memory_usage( <any container type> *cntr );

It would return the amount of (heap) memory associated with cntr.

Use case: when I am writing extension code for Ruby in C, the Ruby runtime likes to know how much memory is associated with a given object (although it doesn't insist on this knowledge). This helps with profiling and (I suspect) the Ruby garbage collector.

JacksonAllan commented 1 year ago

Thanks for the suggestion!

The difficulty of trying to implement this within the library is that there is no standard, cross-platform way to determine the size of the header that malloc and friends place before the allocated block or the padding they place at the end. This problem especially affects list and the planned red-black trees, which store each node in a separate allocation (just counting the memory the library requests would result in a significant underestimate of actual memory consumption). So the issue requires some thought.

In the meantime, I can tell you that the memory usage of each container is as follows:

vecMALLOC_HDR_SIZE + sizeof( cc_vec_hdr_ty ) + cap( &our_vec ) * CC_EL_SIZE( our_vec ) + MALLOC_PADDING
listMALLOC_HDR_SIZE + sizeof( cc_list_hdr_ty ) + MALLOC_PADDING + size( &our_list ) * ( MALLOC_HDR_SIZE + sizeof( cc_listnode_hdr_ty ) + CC_EL_SIZE( our_list ) + MALLOC_PADDING )
mapMALLOC_HDR_SIZE + sizeof( cc_map_hdr_ty ) + cap( &our_map ) * CC_MAP_BUCKET_SIZE( our_map ) + MALLOC_PADDING
setMALLOC_HDR_SIZE + sizeof( cc_map_hdr_ty ) + cap( &our_set ) * CC_SET_BUCKET_SIZE( our_set ) + MALLOC_PADDING

MALLOC_HDR_SIZE is probably 8 or 16 bytes. I'm not sure what MALLOC_PADDING should be. A few minutes of experimentation with malloc_usable_size suggests that malloc on Linux rounds up the memory after the header to 24 + multiples of 16 bytes.

The memory usage of an initialized but not yet used container is effectively zero because init points the container pointer at a global, static placeholder shared among all containers of the same type. The first dynamic allocation only occurs when the first element is added.

rmolinari commented 1 year ago

Wow! Thanks for the quick and detailed reply.

For the purposes of Ruby's runtime, a best effort seems to be enough here. So I'll use your formulas and ignore the header and padding for now. I'm sure this will be fine.

Thanks again.

rmolinari commented 1 year ago

Q: should, say, cc_vec_hdr be cc_vec_hdr_ty ?

JacksonAllan commented 1 year ago

Oops! Sorry. You're right — all those container header structs should be suffixed with _ty. The non-_ty identifiers are the names of internal library functions for accessing the headers. I've updated my earlier response.