ACE icon indicating copy to clipboard operation
ACE copied to clipboard

Get rid of size parameter in `memFree()` ?

Open tehKaiN opened this issue 9 months ago • 6 comments

So I was thinking...

memFree() requires passing size since OS' FreeMem() requires it, but that could be solved by allocating 4 bytes more in front and storing the space there, and let the free function auto-handle it.

This would lead to a bit of waste of the memory (assuming most games don't do more than 1k allocations, it would be at most 4k of RAM). On the other hand, those values are already in the program code (be it constants or calculated) and sometimes they are also kept in structs so that they know how to deallocate themselves. On the other hand, deallocations of same memory chunks in a loop might be more efficient in its current state, because the size would have to be retrieved only once for repeated chunks. That doesn't seem to match most allocation scenarios, though.

This also would be needed for #23, so if the answer is no, then that issue needs to be closed too.

This came to me as I read this thread on EAB that lead me to this post at StackOverflow.

Thoughts?

tehKaiN avatar Jul 26 '25 08:07 tehKaiN

Well, it is catch 22. As you said we either need to store the size ourselves or recalculate it. It would be more user friendly for it to work like most modern platforms which store the size of an object. Maybe it could be a compile time definition if you want to use it.

I would use it since then I don't need to store the size myself one way or the other.

Vairn avatar Jul 26 '25 09:07 Vairn

Personally, I think a little wasted space is worth the convenience.

Normally, there are three ways of tracking the size:

  • it's a constant
  • we give up space and store it in a variable
  • we give up cycles and calculate it

This proposal makes the second two much easier, and memory management less error prone.

steamknight avatar Jul 26 '25 16:07 steamknight

I did a preliminary implementation and tested it on aminer. The results are as follows:

Before: exe 220 696, run to menu in debug: CHIP: 803796, FAST: 642166 After: exe 220 332, run to menu in debug: CHIP: 804088, FAST: 643934 Delta: exe: -364 bytes, CHIP: +292 bytes, FAST: +1768 bytes

Aminer does something around 800 allocations to that point, out of each 500 are simultaneous, so the overhead would be smaller with simpler games.

Is it acceptable waste? I think yes, but I'd like to hear some other opinions.

tehKaiN avatar Aug 25 '25 18:08 tehKaiN

This seems acceptable to me, but if you're worried, I wonder if it would be too much to keep both and gate the functionality behind a a compile-time flag. That way, folks that are concerned can keep doing what they were doing without their code breaking, while those who want to embrace our new size-less memFree() overlords can do so without worry.

steamknight avatar Aug 25 '25 18:08 steamknight

That's theoretically doable with a bit of ifdef magic, but will be cumbersome for library maintainers (me) - basically all ACE code would have to be written with size param in mind. It should also be possible to create a convenience layer for this, but that would also mean going the old way inside ACE.

I'd rather avoid all that by either going the old or new way only.

EDIT: Note that Aminer is quite a big game and those allocations could be unified, especially that in-game messages are stored in a jagged array that's allocating each string separately. Nonetheless it's a sample of some real code and shows the ballpark overhead. It shouldn't get considerably bigger since that's already a large game. Perhaps I should do some more tests with other LMC games.

EDIT 2: I've checked the details and it looks like Aminer's jagged array does approximately 300 fast mem allocs, so that's responsible for 1.2KB memory waste. So for games that don't use such shitty code that would mean something around 300 bytes of wasted chip mem and 500 bytes of fast mem.

tehKaiN avatar Aug 25 '25 18:08 tehKaiN

I've learned that AllocMem allocates memory with 8-byte granularity, so perhaps we could store the size on an UWORD instead with number indicating size's multiples of 8. That would give the 65535 * 8 = ~512k of max allocation size. Still, while such big allocations seem to be unrealistic due to RAM's granularity, technically they're doable on bigger configs, especially on FAST memory.

We could play it out so that highest bit encodes additional multiplier, so that without it the size is encoded with N * 8 yielding max 256k, but with highest bit set it's 256k + N * 512, giving max allocation size equal to 16.5MB.

It would ultimately result with slashing memory waste in half, so for Aminer it would be 1k of memory wasted, of which around 150 bytes are in chip memory. Rounding alloc sizes to multiples of 8 would also make the AllocMem's 8-byte granularity more apparent and allow us optimizing for it in some cases.

tehKaiN avatar Aug 26 '25 07:08 tehKaiN