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
269277func (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.
296308func (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