Working out where the scan plane's 58.7 bytes a key go, on #767, turned up a layout that costs 8 bytes a key more than it needs to.
A node in crates/zu2/src/scan.rs is laid out by hand as key length, then height, then one forward pointer per level, then the key:
const KEY_LEN: usize = 0;
const HEIGHT: usize = 4;
const LINKS: usize = 8;
Both header fields are a u32. The height cannot exceed MAX_HEIGHT, which is 20, so it needs 5 bits and is using 32. That is the first four bytes.
The second four are the round up. Arena::alloc rounds every request to a multiple of 8, because the next node's base has to stay 8 aligned for its links to be. A height one node with a go-ycsb key is 8 + 8 + 33 = 49 bytes, which rounds to 56, so seven of those bytes are padding. Take the header down to 4 and the same node is 45 bytes rounding to 48, and the padding is three. Both the four bytes and most of the padding come back.
The header cannot simply shrink where it is, because LINKS would become 4 and the forward pointers would stop being 8 aligned. So the links move to the front:
[8 * height forward pointers][u32: height in 5 bits, key length in 27][key bytes]
The node base is 8 aligned, so link zero is at offset 0 and every link after it is 8 aligned by construction. The header lands at 8 * height, which is 8 aligned and so certainly 4 aligned. The key needs no alignment at all. height_of and key_of both have to read the header before they can find anything else, which they already do, and link gets simpler rather than harder.
Cost of the change: 27 bits of key length is 128 MiB, and a key that long is already past what Options will accept, but the encode should assert rather than truncate.
Effect, weighted by the geometric height distribution at one over four and a 32.9 byte go-ycsb key: the mean node goes from 57.8 bytes to 50.6, so the plane goes from 58.7 bytes a key to about 51.5, 14 percent off. On the server2 million key run that is 56.0 MiB down to about 49.
Worth doing because it is confined to four constants and three accessors, and it does not change the walk: same number of dereferences, same cache lines, one fewer word in front of the pointers the walk actually reads. If anything the walk gets slightly better, since at height one the links and the header now share the first cache line with the start of the key instead of being pushed along by the header.
Working out where the scan plane's 58.7 bytes a key go, on #767, turned up a layout that costs 8 bytes a key more than it needs to.
A node in
crates/zu2/src/scan.rsis laid out by hand as key length, then height, then one forward pointer per level, then the key:Both header fields are a
u32. The height cannot exceedMAX_HEIGHT, which is 20, so it needs 5 bits and is using 32. That is the first four bytes.The second four are the round up.
Arena::allocrounds every request to a multiple of 8, because the next node's base has to stay 8 aligned for its links to be. A height one node with a go-ycsb key is 8 + 8 + 33 = 49 bytes, which rounds to 56, so seven of those bytes are padding. Take the header down to 4 and the same node is 45 bytes rounding to 48, and the padding is three. Both the four bytes and most of the padding come back.The header cannot simply shrink where it is, because
LINKSwould become 4 and the forward pointers would stop being 8 aligned. So the links move to the front:The node base is 8 aligned, so link zero is at offset 0 and every link after it is 8 aligned by construction. The header lands at
8 * height, which is 8 aligned and so certainly 4 aligned. The key needs no alignment at all.height_ofandkey_ofboth have to read the header before they can find anything else, which they already do, andlinkgets simpler rather than harder.Cost of the change: 27 bits of key length is 128 MiB, and a key that long is already past what
Optionswill accept, but the encode should assert rather than truncate.Effect, weighted by the geometric height distribution at one over four and a 32.9 byte go-ycsb key: the mean node goes from 57.8 bytes to 50.6, so the plane goes from 58.7 bytes a key to about 51.5, 14 percent off. On the server2 million key run that is 56.0 MiB down to about 49.
Worth doing because it is confined to four constants and three accessors, and it does not change the walk: same number of dereferences, same cache lines, one fewer word in front of the pointers the walk actually reads. If anything the walk gets slightly better, since at height one the links and the header now share the first cache line with the start of the key instead of being pushed along by the header.