Skip to content

zu2: a scan plane node spends 8 bytes on a header that fits in 4, and pays for it twice #772

Description

@tamnd

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    perfPerformance and resource budgetsstorageStorage engines and file formats

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions