Skip to content

Commit 6cde005

Browse files
committed
runtime: avoid zeroing scavenged memory
On Linux, memory returned to the kernel via MADV_DONTNEED is guaranteed to be zero-filled on its next use. This commit leverages this kernel behavior to avoid a redundant software zeroing pass in the runtime, improving performance. Signed-off-by: Lance Yang <[email protected]>
1 parent 12ec09f commit 6cde005

File tree

10 files changed

+106
-0
lines changed

10 files changed

+106
-0
lines changed

src/runtime/mem.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,12 @@ func sysUnused(v unsafe.Pointer, n uintptr) {
7070
sysUnusedOS(v, n)
7171
}
7272

73+
// sysNeedZeroAfterUnused reports whether memory returned by sysUnused must be
74+
// zeroed for use.
75+
func sysNeedZeroAfterUnused() bool {
76+
return sysNeedZeroAfterUnusedOS()
77+
}
78+
7379
// sysUsed transitions a memory region from Prepared to Ready. It notifies the
7480
// operating system that the memory region is needed and ensures that the region
7581
// may be safely accessed. This is typically a no-op on systems that don't have

src/runtime/mem_aix.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,7 @@ func sysMapOS(v unsafe.Pointer, n uintptr, _ string) {
7979
throw("runtime: cannot map pages in arena address space")
8080
}
8181
}
82+
83+
func sysNeedZeroAfterUnusedOS() bool {
84+
return true
85+
}

src/runtime/mem_bsd.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,7 @@ func sysMapOS(v unsafe.Pointer, n uintptr, _ string) {
8585
throw("runtime: cannot map pages in arena address space")
8686
}
8787
}
88+
89+
func sysNeedZeroAfterUnusedOS() bool {
90+
return true
91+
}

src/runtime/mem_darwin.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,7 @@ func sysMapOS(v unsafe.Pointer, n uintptr, _ string) {
7474
throw("runtime: cannot map pages in arena address space")
7575
}
7676
}
77+
78+
func sysNeedZeroAfterUnusedOS() bool {
79+
return true
80+
}

src/runtime/mem_linux.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,3 +188,7 @@ func sysMapOS(v unsafe.Pointer, n uintptr, vmaName string) {
188188
sysNoHugePageOS(v, n)
189189
}
190190
}
191+
192+
func sysNeedZeroAfterUnusedOS() bool {
193+
return debug.madvdontneed == 0
194+
}

src/runtime/mem_sbrk.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,3 +296,7 @@ func sysReserveAlignedSbrk(size, align uintptr) (unsafe.Pointer, uintptr) {
296296
})
297297
return unsafe.Pointer(p), size
298298
}
299+
300+
func sysNeedZeroAfterUnusedOS() bool {
301+
return true
302+
}

src/runtime/mem_windows.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,3 +132,7 @@ func sysReserveOS(v unsafe.Pointer, n uintptr, _ string) unsafe.Pointer {
132132

133133
func sysMapOS(v unsafe.Pointer, n uintptr, _ string) {
134134
}
135+
136+
func sysNeedZeroAfterUnusedOS() bool {
137+
return true
138+
}

src/runtime/mheap.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1074,6 +1074,13 @@ func (h *mheap) setSpans(base, npage uintptr, s *mspan) {
10741074
//
10751075
// There are no locking constraints on this method.
10761076
func (h *mheap) allocNeedsZero(base, npage uintptr) (needZero bool) {
1077+
// If these pages were scavenged (returned to the OS), the kernel guarantees
1078+
// they will be zero-filled on next use (fault-in), so we can treat them as
1079+
// already zeroed and skip explicit clearing.
1080+
if !sysNeedZeroAfterUnused() && h.pages.isScavenged(base, npage) {
1081+
return false
1082+
}
1083+
10771084
for npage > 0 {
10781085
ai := arenaIndex(base)
10791086
ha := h.arenas[ai.l1()][ai.l2()]

src/runtime/mpagealloc.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,39 @@ func (p *pageAlloc) enableChunkHugePages() {
481481
}
482482
}
483483

484+
// isScavenged returns true if the page range [base, base+npages*pageSize) is
485+
// fully scavenged.
486+
//
487+
// p.mheapLock must be held.
488+
func (p *pageAlloc) isScavenged(base, npages uintptr) bool {
489+
limit := base + npages*pageSize - 1
490+
sc, ec := chunkIndex(base), chunkIndex(limit)
491+
si, ei := chunkPageIndex(base), chunkPageIndex(limit)
492+
493+
if sc == ec {
494+
// The range doesn't cross any chunk boundaries.
495+
chunk := p.chunkOf(sc)
496+
return chunk.scavenged.isRangeSet(si, ei+1-si)
497+
}
498+
// The range crosses at least one chunk boundary.
499+
chunk := p.chunkOf(sc)
500+
if !chunk.scavenged.isRangeSet(si, pallocChunkPages-si) {
501+
return false
502+
}
503+
for c := sc + 1; c < ec; c++ {
504+
chunk := p.chunkOf(c)
505+
if !chunk.scavenged.isRangeSet(0, pallocChunkPages) {
506+
return false
507+
}
508+
}
509+
chunk = p.chunkOf(ec)
510+
if !chunk.scavenged.isRangeSet(0, ei+1) {
511+
return false
512+
}
513+
return true
514+
}
515+
516+
484517
// update updates heap metadata. It must be called each time the bitmap
485518
// is updated.
486519
//

src/runtime/mpallocbits.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,42 @@ func (b *pageBits) setAll() {
5757
}
5858
}
5959

60+
// isRangeSet returns true if all bits in the range [i, i+n) are set.
61+
func (b *pageBits) isRangeSet(i, n uint) bool {
62+
if n == 0 {
63+
return true
64+
}
65+
// Check bits [i, j].
66+
j := i + n - 1
67+
if i/64 == j/64 {
68+
// Fast path: the range fits in a single uint64.
69+
mask := ((uint64(1) << n) - 1) << (i % 64)
70+
return b[i/64]&mask == mask
71+
}
72+
73+
// Slow path: the range spans multiple uint64s.
74+
75+
// Check leading bits.
76+
if b[i/64]>>(i%64) != ^uint64(0)>>(i%64) {
77+
return false
78+
}
79+
80+
// Check full uint64s in the middle.
81+
for k := i/64 + 1; k < j/64; k++ {
82+
if b[k] != ^uint64(0) {
83+
return false
84+
}
85+
}
86+
87+
// Check trailing bits.
88+
mask := (uint64(1) << (j%64 + 1)) - 1
89+
if b[j/64]&mask != mask {
90+
return false
91+
}
92+
93+
return true
94+
}
95+
6096
// setBlock64 sets the 64-bit aligned block of bits containing the i'th bit that
6197
// are set in v.
6298
func (b *pageBits) setBlock64(i uint, v uint64) {

0 commit comments

Comments
 (0)