Skip to content

Commit f39bc3b

Browse files
committed
reduce memory allocations by using bitset instead of sync.map
1 parent cde821a commit f39bc3b

File tree

1 file changed

+38
-19
lines changed
  • packages/orchestrator/pkg/sandbox/block

1 file changed

+38
-19
lines changed

packages/orchestrator/pkg/sandbox/block/cache.go

Lines changed: 38 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"math"
99
"math/rand"
1010
"os"
11-
"slices"
1211
"sync"
1312
"sync/atomic"
1413
"syscall"
@@ -49,7 +48,8 @@ type Cache struct {
4948
blockSize int64
5049
mmap *mmap.MMap
5150
mu sync.RWMutex
52-
dirty sync.Map
51+
dirty *bitset.BitSet
52+
dirtyMu sync.RWMutex // protects dirty bitset for concurrent access
5353
dirtyFile bool
5454
closed atomic.Bool
5555
}
@@ -68,6 +68,7 @@ func NewCache(size, blockSize int64, filePath string, dirtyFile bool) (*Cache, e
6868
filePath: filePath,
6969
size: size,
7070
blockSize: blockSize,
71+
dirty: bitset.New(0),
7172
dirtyFile: dirtyFile,
7273
}, nil
7374
}
@@ -87,11 +88,14 @@ func NewCache(size, blockSize int64, filePath string, dirtyFile bool) (*Cache, e
8788
return nil, fmt.Errorf("error mapping file: %w", err)
8889
}
8990

91+
totalBlocks := uint(header.TotalBlocks(size, blockSize))
92+
9093
return &Cache{
9194
mmap: &mm,
9295
filePath: filePath,
9396
size: size,
9497
blockSize: blockSize,
98+
dirty: bitset.New(totalBlocks),
9599
dirtyFile: dirtyFile,
96100
}, nil
97101
}
@@ -253,12 +257,16 @@ func (c *Cache) isCached(off, length int64) bool {
253257

254258
// Cap if the length goes beyond the cache size, so we don't check for blocks that are out of bounds.
255259
end := min(off+length, c.size)
256-
// Recalculate the length based on the capped end, so we check for the correct blocks in case of capping.
257-
length = end - off
258260

259-
for _, blockOff := range header.BlocksOffsets(length, c.blockSize) {
260-
_, dirty := c.dirty.Load(off + blockOff)
261-
if !dirty {
261+
// Check all blocks in the range
262+
startBlock := uint(header.BlockIdx(off, c.blockSize))
263+
endBlock := uint(header.BlockIdx(end-1, c.blockSize))
264+
265+
c.dirtyMu.RLock()
266+
defer c.dirtyMu.RUnlock()
267+
268+
for i := startBlock; i <= endBlock; i++ {
269+
if !c.dirty.Test(i) {
262270
return false
263271
}
264272
}
@@ -267,9 +275,14 @@ func (c *Cache) isCached(off, length int64) bool {
267275
}
268276

269277
func (c *Cache) setIsCached(off, length int64) {
270-
for _, blockOff := range header.BlocksOffsets(length, c.blockSize) {
271-
c.dirty.Store(off+blockOff, struct{}{})
278+
startBlock := uint(header.BlockIdx(off, c.blockSize))
279+
endBlock := uint(header.BlockIdx(off+length-1, c.blockSize))
280+
281+
c.dirtyMu.Lock()
282+
for i := startBlock; i <= endBlock; i++ {
283+
c.dirty.Set(i)
272284
}
285+
c.dirtyMu.Unlock()
273286
}
274287

275288
// When using WriteAtWithoutLock you must ensure thread safety, ideally by only writing to the same block once and the exposing the slice.
@@ -291,16 +304,18 @@ func (c *Cache) WriteAtWithoutLock(b []byte, off int64) (int, error) {
291304
return n, nil
292305
}
293306

294-
// dirtySortedKeys returns a sorted list of dirty keys.
295-
// Key represents a block offset.
307+
// dirtySortedKeys returns a sorted list of dirty block offsets.
296308
func (c *Cache) dirtySortedKeys() []int64 {
297-
var keys []int64
298-
c.dirty.Range(func(key, _ any) bool {
299-
keys = append(keys, key.(int64))
309+
c.dirtyMu.RLock()
310+
defer c.dirtyMu.RUnlock()
311+
312+
// Pre-allocate with estimated capacity
313+
keys := make([]int64, 0, c.dirty.Count())
300314

301-
return true
302-
})
303-
slices.Sort(keys)
315+
// Iterate set bits in order (bitset iteration is already sorted)
316+
for i, ok := c.dirty.NextSet(0); ok; i, ok = c.dirty.NextSet(i + 1) {
317+
keys = append(keys, header.BlockOffset(int64(i), c.blockSize))
318+
}
304319

305320
return keys
306321
}
@@ -491,9 +506,13 @@ func (c *Cache) copyProcessMemory(
491506
return fmt.Errorf("failed to read memory: expected %d bytes, got %d", segmentSize, n)
492507
}
493508

494-
for _, blockOff := range header.BlocksOffsets(segmentSize, c.blockSize) {
495-
c.dirty.Store(offset+blockOff, struct{}{})
509+
startBlock := uint(header.BlockIdx(offset, c.blockSize))
510+
endBlock := uint(header.BlockIdx(offset+segmentSize-1, c.blockSize))
511+
c.dirtyMu.Lock()
512+
for i := startBlock; i <= endBlock; i++ {
513+
c.dirty.Set(i)
496514
}
515+
c.dirtyMu.Unlock()
497516

498517
offset += segmentSize
499518

0 commit comments

Comments
 (0)