Skip to content

Commit 130c828

Browse files
ValentaTomasclaudedjeebus
authored andcommitted
perf(shared): derive mapping run lengths from offsets and narrow the build index
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: Joe Lombrozo <joe.lombrozo@e2b.dev> GitOrigin-RevId: c00d17c20ea3656abc6af8dab37ff65a45b6d64a
1 parent e940ec7 commit 130c828

6 files changed

Lines changed: 499 additions & 74 deletions

File tree

‎packages/shared/pkg/storage/header/compact.go‎

Lines changed: 146 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,42 @@ import (
1010
)
1111

1212
// Mapping is the compact in-memory representation of a Header's mapping list.
13-
// A merged Header is cached for up to 25h, so on snapshot-heavy nodes these
14-
// slices dominate host RAM. It shrinks each entry from 40 bytes (a BuildMap) to
15-
// 14 by encoding offset/length/storage as uint32 block indices, deduplicating
16-
// BuildId into a per-header table addressed by uint16, and storing the columns
17-
// as parallel slices. Immutable; read via At / All / Slice.
13+
// A merged Header is cached for hours, so on snapshot-heavy nodes these slices
14+
// dominate host RAM. It shrinks each entry from 40 bytes (a BuildMap) to 9 or
15+
// 10 by encoding offset/storage as uint32 block indices, deduplicating BuildId
16+
// into a per-header table addressed by one byte (two once a header references
17+
// more than 255 builds), storing the columns as parallel slices, and deriving
18+
// each entry's length from where the next one starts. Immutable; read via
19+
// At / All / Slice.
1820
type Mapping struct {
1921
blockSize uint64
2022
builds []uuid.UUID
2123
offsets []uint32
22-
lengths []uint32
23-
storage []uint32
24-
buildIdx []uint16
24+
// lengths is nil when every entry starts where the previous one ends: the
25+
// length is then offsets[i+1]-offsets[i], and endBlocks-offsets[i] for the
26+
// last entry. Only a mapping with gaps or overlaps between entries (legacy
27+
// headers predating NormalizeFixVersion) stores the column explicitly.
28+
lengths []uint32
29+
storage []uint32
30+
// Exactly one build-index column is set for a non-empty mapping: buildIdx8
31+
// while the header references at most maxBuilds8 builds, buildIdx16
32+
// otherwise. Each addresses builds, with its own sentinel for an empty
33+
// (zero) region.
34+
buildIdx8 []uint8
35+
buildIdx16 []uint16
36+
// endBlocks is the block at which the last entry ends.
37+
endBlocks uint64
2538
}
2639

2740
const (
28-
nilBuildIdx = math.MaxUint16
29-
30-
// maxBuildsPerHeader leaves nilBuildIdx reserved for empty regions.
31-
maxBuildsPerHeader = nilBuildIdx
41+
nilBuildIdx8 = math.MaxUint8
42+
nilBuildIdx16 = math.MaxUint16
43+
44+
// maxBuilds8 is how many builds the one-byte index column can address,
45+
// with nilBuildIdx8 reserved for empty regions.
46+
maxBuilds8 = nilBuildIdx8
47+
// maxBuildsPerHeader leaves nilBuildIdx16 reserved for empty regions.
48+
maxBuildsPerHeader = nilBuildIdx16
3249
)
3350

3451
// maxBlockIdx is the largest block index representable by the uint32 columns.
@@ -50,10 +67,13 @@ func NewMapping(blockSize uint64, src []BuildMap) (Mapping, error) {
5067
idxByBuild := make(map[uuid.UUID]uint16, 8)
5168
builds := make([]uuid.UUID, 0, 8)
5269
offsets := make([]uint32, len(src))
53-
lengths := make([]uint32, len(src))
5470
storage := make([]uint32, len(src))
71+
// Wide scratch column; narrowed to one byte per entry once the build count
72+
// is known (see setBuildIdx).
5573
buildIdx := make([]uint16, len(src))
5674

75+
contiguous := true
76+
var prevEnd uint64
5777
for i, m := range src {
5878
if m.Offset%blockSize != 0 {
5979
return Mapping{}, fmt.Errorf("compact mapping: offset %d at index %d not block-aligned to %d", m.Offset, i, blockSize)
@@ -71,8 +91,12 @@ func NewMapping(blockSize uint64, src []BuildMap) (Mapping, error) {
7191
if offBlocks > maxBlockIdx || lenBlocks > maxBlockIdx || stoBlocks > maxBlockIdx {
7292
return Mapping{}, fmt.Errorf("compact mapping: block index out of uint32 range at entry %d", i)
7393
}
94+
if i > 0 && offBlocks != prevEnd {
95+
contiguous = false
96+
}
97+
prevEnd = offBlocks + lenBlocks
7498

75-
idx := uint16(nilBuildIdx)
99+
idx := uint16(nilBuildIdx16)
76100
if m.BuildId != uuid.Nil {
77101
var ok bool
78102
idx, ok = idxByBuild[m.BuildId]
@@ -87,46 +111,98 @@ func NewMapping(blockSize uint64, src []BuildMap) (Mapping, error) {
87111
}
88112

89113
offsets[i] = uint32(offBlocks)
90-
lengths[i] = uint32(lenBlocks)
91114
storage[i] = uint32(stoBlocks)
92115
buildIdx[i] = idx
93116
}
94117

95-
return Mapping{
118+
out := Mapping{
96119
blockSize: blockSize,
97120
builds: builds,
98121
offsets: offsets,
99-
lengths: lengths,
100122
storage: storage,
101-
buildIdx: buildIdx,
102-
}, nil
123+
endBlocks: prevEnd,
124+
}
125+
out.buildIdx8, out.buildIdx16 = buildIdxColumns(len(builds), buildIdx)
126+
if !contiguous {
127+
out.lengths = make([]uint32, len(src))
128+
for i, m := range src {
129+
out.lengths[i] = uint32(m.Length / blockSize)
130+
}
131+
}
132+
133+
return out, nil
103134
}
104135

105-
// newMappingFromColumns builds a Mapping from already-decoded columns,
106-
// avoiding the []BuildMap intermediate on the deserialize path. All column
107-
// slices must have the same length, and every buildIdx must index builds.
108-
func newMappingFromColumns(blockSize uint64, builds []uuid.UUID, offsets, lengths, storage []uint32, buildIdx []uint16) (Mapping, error) {
136+
// buildIdxColumns chooses the retained build-index column from its wide form:
137+
// one byte per entry when every build fits, which every memfile header and
138+
// nearly every rootfs header does, otherwise the wide column as is. Exactly
139+
// one of the returned columns is non-nil.
140+
func buildIdxColumns(nBuilds int, wide []uint16) ([]uint8, []uint16) {
141+
if nBuilds > maxBuilds8 {
142+
return nil, wide
143+
}
144+
145+
narrow := make([]uint8, len(wide))
146+
for i, idx := range wide {
147+
if idx == nilBuildIdx16 {
148+
narrow[i] = nilBuildIdx8
149+
} else {
150+
narrow[i] = uint8(idx)
151+
}
152+
}
153+
154+
return narrow, nil
155+
}
156+
157+
// buildIndex returns the i-th entry's index into builds, or -1 for an empty
158+
// region.
159+
func (m Mapping) buildIndex(i int) int {
160+
if m.buildIdx8 != nil {
161+
if idx := m.buildIdx8[i]; idx != nilBuildIdx8 {
162+
return int(idx)
163+
}
164+
165+
return -1
166+
}
167+
if idx := m.buildIdx16[i]; idx != nilBuildIdx16 {
168+
return int(idx)
169+
}
170+
171+
return -1
172+
}
173+
174+
// newMappingFromColumns builds a Mapping from already-decoded columns, avoiding
175+
// the []BuildMap intermediate on the deserialize path. The entries must be
176+
// contiguous, with the last one ending at endBlocks, so no lengths column is
177+
// stored. All column slices must have the same length, and every buildIdx must
178+
// index builds.
179+
func newMappingFromColumns(blockSize uint64, builds []uuid.UUID, offsets, storage []uint32, buildIdx []uint16, endBlocks uint64) (Mapping, error) {
109180
n := len(offsets)
110-
if len(lengths) != n || len(storage) != n || len(buildIdx) != n {
111-
return Mapping{}, fmt.Errorf("compact mapping: column length mismatch (offsets=%d lengths=%d storage=%d buildIdx=%d)", n, len(lengths), len(storage), len(buildIdx))
181+
if len(storage) != n || len(buildIdx) != n {
182+
return Mapping{}, fmt.Errorf("compact mapping: column length mismatch (offsets=%d storage=%d buildIdx=%d)", n, len(storage), len(buildIdx))
183+
}
184+
if n > 0 && uint64(offsets[n-1]) > endBlocks {
185+
return Mapping{}, fmt.Errorf("compact mapping: last offset block %d beyond end block %d", offsets[n-1], endBlocks)
112186
}
113187
for i, bi := range buildIdx {
114-
if bi == nilBuildIdx {
188+
if bi == nilBuildIdx16 {
115189
continue
116190
}
117191
if int(bi) >= len(builds) {
118192
return Mapping{}, fmt.Errorf("compact mapping: buildIdx %d at entry %d out of range (%d builds)", bi, i, len(builds))
119193
}
120194
}
121195

122-
return Mapping{
196+
out := Mapping{
123197
blockSize: blockSize,
124198
builds: builds,
125199
offsets: offsets,
126-
lengths: lengths,
127200
storage: storage,
128-
buildIdx: buildIdx,
129-
}, nil
201+
endBlocks: endBlocks,
202+
}
203+
out.buildIdx8, out.buildIdx16 = buildIdxColumns(len(builds), buildIdx)
204+
205+
return out, nil
130206
}
131207

132208
// Len returns the number of entries.
@@ -137,11 +213,19 @@ func (m Mapping) BlockSize() uint64 { return m.blockSize }
137213

138214
// ByteSize returns the approximate heap footprint of the mapping's columns, so
139215
// callers can gauge how much RAM cached headers hold without knowing the
140-
// encoding. It counts the four parallel columns (4+4+4+2 bytes per entry) plus
141-
// the deduplicated build table (16 bytes per UUID); it excludes the struct
142-
// header itself and any slice capacity beyond len.
216+
// encoding. It counts the per-entry columns — 9 bytes for the contiguous form,
217+
// 4 more where lengths are stored explicitly and one more where the header
218+
// needs the wide build index — plus the deduplicated build table (16 bytes per
219+
// UUID); it excludes the struct header itself and any slice capacity beyond
220+
// len.
143221
func (m Mapping) ByteSize() int {
144-
const bytesPerEntry = 4 + 4 + 4 + 2 // offsets, lengths, storage, buildIdx
222+
bytesPerEntry := 4 + 4 + 1 // offsets, storage, build index
223+
if m.lengths != nil {
224+
bytesPerEntry += 4
225+
}
226+
if m.buildIdx16 != nil {
227+
bytesPerEntry++
228+
}
145229

146230
return len(m.offsets)*bytesPerEntry + len(m.builds)*16
147231
}
@@ -164,17 +248,34 @@ func (m Mapping) SharesStorageWith(other Mapping) bool {
164248
// returned slice is shared with the Mapping; callers must not mutate it.
165249
func (m Mapping) Builds() []uuid.UUID { return m.builds }
166250

251+
// Contiguous reports whether every entry starts where the previous one ends.
252+
// Such a mapping derives its lengths from the offsets column instead of
253+
// storing them.
254+
func (m Mapping) Contiguous() bool { return m.lengths == nil }
255+
256+
// lengthBlocks returns the i-th entry's length in blocks.
257+
func (m Mapping) lengthBlocks(i int) uint32 {
258+
if m.lengths != nil {
259+
return m.lengths[i]
260+
}
261+
if i+1 < len(m.offsets) {
262+
return m.offsets[i+1] - m.offsets[i]
263+
}
264+
265+
return uint32(m.endBlocks - uint64(m.offsets[i]))
266+
}
267+
167268
// At materializes the i-th entry as a BuildMap. Panics if i is out of range,
168269
// matching `mapping[i]` semantics.
169270
func (m Mapping) At(i int) BuildMap {
170271
buildID := uuid.Nil
171-
if m.buildIdx[i] != nilBuildIdx {
172-
buildID = m.builds[m.buildIdx[i]]
272+
if bi := m.buildIndex(i); bi >= 0 {
273+
buildID = m.builds[bi]
173274
}
174275

175276
return BuildMap{
176277
Offset: uint64(m.offsets[i]) * m.blockSize,
177-
Length: uint64(m.lengths[i]) * m.blockSize,
278+
Length: uint64(m.lengthBlocks(i)) * m.blockSize,
178279
BuildId: buildID,
179280
BuildStorageOffset: uint64(m.storage[i]) * m.blockSize,
180281
}
@@ -193,15 +294,15 @@ func (m Mapping) All() iter.Seq2[int, BuildMap] {
193294
}
194295

195296
// BytesByBuild sums the bytes attributed to each referenced build. It scans the
196-
// length and buildIdx columns directly (no BuildMap materialization or per-entry
197-
// uuid hashing), accumulating into a small per-build slice, so it stays cheap
198-
// even for mappings with millions of entries. Empty (nil-build) regions are
199-
// skipped. The returned map is non-nil and addressable by the caller.
297+
// columns directly (no BuildMap materialization or per-entry uuid hashing),
298+
// accumulating into a small per-build slice, so it stays cheap even for
299+
// mappings with millions of entries. Empty (nil-build) regions are skipped.
300+
// The returned map is non-nil and addressable by the caller.
200301
func (m Mapping) BytesByBuild() map[uuid.UUID]uint64 {
201302
sums := make([]uint64, len(m.builds))
202-
for i, bi := range m.buildIdx {
203-
if bi != nilBuildIdx {
204-
sums[bi] += uint64(m.lengths[i])
303+
for i := range m.offsets {
304+
if bi := m.buildIndex(i); bi >= 0 {
305+
sums[bi] += uint64(m.lengthBlocks(i))
205306
}
206307
}
207308

@@ -242,7 +343,7 @@ func (m Mapping) Validate(size, blockSize uint64) error {
242343
var currentOffset uint64
243344
for i := range m.offsets {
244345
offset := uint64(m.offsets[i]) * m.blockSize
245-
length := uint64(m.lengths[i]) * m.blockSize
346+
length := uint64(m.lengthBlocks(i)) * m.blockSize
246347
if currentOffset != offset {
247348
return fmt.Errorf("mapping validation failed at index %d: expected offset %d (block %d), got %d (block %d)", i, currentOffset, currentOffset/blockSize, offset, offset/blockSize)
248349
}

0 commit comments

Comments
 (0)