mirror of
https://github.com/libretro/parallel-ps2.git
synced 2026-11-09 17:28:25 -10:00
GSNodeAllocator kept a free list per node type in C++, and every node the list did not have came from operator new. The free list is now a C89 gs_node_pool in GSObjectPool.h and .c, beside the object pool, and GSNodeAllocator is only the wrapper the standard containers need. Take and give are static in the header and compile to the same instructions as before: five for take, three for give, which is as short as a free list gets. What changed is the empty list. Out of line, the pool hands out the newest slab's never-used nodes in address order and makes a 16 KB slab when that runs out. One heap call covers 185 nodes of the hash cache's 88 bytes instead of one per node, and no pass links a slab onto the free list before it is used. Out of memory it aborts, as operator new does without exceptions, so take has no null check. callgrind, 10,000 nodes filled, retaken 20 times, then 5 million take/give pairs: the caller's code runs the same 44,795,372 instructions either way. Making nodes took about 2,000,000 instructions in malloc and operator new before, and takes 151,489 in the refill now: 49.47 million instructions in all became 47.53 million. Timed (medians of 9 runs, 200,000 nodes): - first fill: 13.0 ms to 8.4 ms; - scattered retake: 450 ms to 367 ms, the nodes now packed in slabs; - hot take/give: unchanged. The slabs used to stay until exit. gs_node_pools_release frees those of every pool whose nodes are all back, and the HW renderer calls it once the texture cache is gone. tests/gspool checks the node pools: nodes aligned, apart and over more than one slab; a node given back is taken again; release keeps a pool's slabs while one node is out and frees them once all are back. The object pool's double-free block now skips address sanitizer builds, where that pool hands out nothing and its takes could not succeed.