That’s a different type of heap. Those heaps satisfy the min-heap or max-heap properties that parents nodes are either respectively smaller or larger than their children. Siblings and cousins have no guarantee of structure but the smallest or largest node is always on top. To find the next smallest/largest one need only look at its children.
The name collision between heap data structures and the heap is truly unfortunate. Look at a StackOverflow question I see that no one else can see a clear reason for the name collision besides a “heap” in the traditional sense being a messy pile of something. A heap data structure isn’t completely ordered and the heap is a very, very messy place.
As for Lua’s heap… A better question would probably be how Lua’s garbage collector works. I found a nice article here that explained a lot of it. Every object in Lua is part of two structures, a graph and a linked list. The graph is the graph of tables that make up the Lua environment with the connections being the links between tables. The linked list is a single, long linked list between every object in the Lua state. Every once in awhile the garbage collector pauses Lua and traverses the table graph, recursively marking objects. If it encounters an already marked object then it doesn’t mark it again or visit its connections. Once every object connected to the root of the graph is marked, it runs through the linked list of every object in Lua, removing every object from the linked list that isn’t marked.
At least that is how Lua version 5.0 worked. 5.1 introduced an incremental garbage collector. Basically it had three marks instead of two, marked, unmarked, and fringe. Marked nodes and unmarked nodes have to be separated by the fringe nodes, and any mutation that occurs between steps needs to keep this true. This means that every assignment needs to check if it violates this rule and if so the garbage collector runs to correct it.
This method of garbage collection means that Lua doesn’t have to keep track of the number of references to objects like some scripting languages do, meaning that there is hopefully less overhead and in general Lua runs faster. One of the problems with this method of garbage collection is that it is latency bound by pointer chasing, being heavily graph based, whereas traditional reference counting takes place when the object is presumably already in a CPU cache. The advantage over reference counting is that extra work isn’t done as the reference count fluctuates in between collection cycles.
More specifically for tables, when a table is created a Table
structure is filled out:
typedef struct Table {
CommonHeader; // This contains the linked list of all objects
lu_byte flags; // I'm not entirely sure what these tags are used for
lu_byte lsizenode; /* log2 of size of `node' (hash table) array */
struct Table *metatable;
TValue *array; /* array part */
Node *node; // the hash table
Node *lastfree; /* any free position is before this position */
GCObject *gclist;
int sizearray; /* size of `array' array */
} Table;
The Node structures are a key and value pair, both of which have garbage collection links too. The Key is composed of a value and a optional link to another key in case of hash collisions. Check it out here:
https://www.lua.org/source/5.1/lobject.h.html#Table