Repository navigation
Conversation
glibc keeps a freed buffer's memory in its heap, unless it had mapped the buffer on its own. It maps buffers of 128 KB or more on their own at first, but each time one is freed it raises that size to the freed buffer's, up to 32 MB on 64 bits, so later buffers of those sizes come from the heap and stay there once freed. After a burst of large allocations the process stayed as large as the burst, although the collector had freed the buffers and MEM_INFO_RESERVED no longer counted them. In an OpenFL game that reads 17 MB texture files while a level loads, glibc held 722 MB of free memory once the level was loaded, out of 1740 MB of anonymous memory. With this change the same point is at 1160 MB. The collector now counts what it frees to the C heap, the large buffers and the block groups, and calls malloc_trim once that reaches 16 MB. The trim runs on the thread that frees dropped buffers, after its batch, so no other thread waits for it: it took up to 26 ms in the game, for 360 MB. A collection that frees its buffers itself, as a compacting one does, trims before it returns. A collection with no buffers to drop still wakes the thread when a trim is due, for what the sweep freed. Tests: test/regression/LargeFreedMemoryReturned drops 384 MB of 4 MB buffers, after freeing some 8 MB ones so that glibc takes them from its heap. It stayed at 396 MB resident and now returns to 12 MB. Where /proc/self/statm is missing it passes without measuring. The other Large*, Gc* and Generational* cases, ReleaseBlockSizedLarge, MemInfoLiveBytes and ManagedMemoryOverRelease pass, 47 in all. Co-Authored-By: Claude Fable 5.2 <[email protected]>
HomuHomu833
approved these changes
Nov 11, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
glibc keeps a freed buffer's memory in its heap, unless it had mapped the buffer on its own. It maps
buffers of 128 KB or more on their own at first, but each time one is freed it raises that size to
the freed buffer's, up to 32 MB on 64 bits, so later buffers of those sizes come from the heap and
stay there once freed. After a burst of large allocations the process stayed as large as the burst,
although the collector had freed the buffers and
MEM_INFO_RESERVEDno longer counted them.In an OpenFL game that reads 17 MB texture files while a level loads,
mallinfo2showed 722 MB offree memory held in glibc's heap once the level was loaded, out of 1740 MB of anonymous memory, and
a
malloc_trim(0)at that point brought it down to 1117 MB. SettingM_MMAP_THRESHOLDalone(128 KB) left 263 MB free in the heap, from the buffers under that size.
The collector now counts what it frees to the C heap, the large buffers and the block groups, and
calls
malloc_trimonce that reaches 16 MB. The trim runs on the thread that frees dropped buffers,after its batch, so no other thread waits for it. A collection that frees its buffers itself, as a
compacting one does, trims before it returns, and a collection with no buffers to drop still wakes
the thread when a trim is due, for what the sweep freed. Nothing changes without glibc.
In the game, over a two minute run through a town and a level: 23 trims of 0.4 to 26 ms each (the
longest for 360 MB), resident memory in the level from 2106 MB to 1574 MB, and no frame over 9 ms
with or without the change.
Tests:
test/regression/LargeFreedMemoryReturneddrops 384 MB of 4 MB buffers, after freeing some8 MB ones so that glibc takes them from its heap. It stayed at 396 MB resident and now returns to
12 MB. Where
/proc/self/statmis missing it passes without measuring. The otherLarge*,Gc*and
Generational*cases,ReleaseBlockSizedLarge,MemInfoLiveBytesandManagedMemoryOverReleasepass, 47 in all (Linux, g++ 14,-D HXCPP_M64).