Files
libretroadmin c9e11bde82 GS: node pools in C89, nodes carved from slabs
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.
2026-11-02 12:06:56 +01:00
..
2026-09-20 01:51:15 +02:00