Skip to content

Commit 4517c02

Browse files
committed
runtime: add per-p mspan cache
This change adds a per-p mspan object cache similar to the sudog cache. Unfortunately this cache can't quite operate like the sudog cache, since it is used in contexts where write barriers are disallowed (i.e. allocation codepaths), so rather than managing an array and a slice, it's just an array and a length. A little bit more unsafe, but avoids any write barriers. The purpose of this change is to reduce the number of operations which require the heap lock in allocation, paving the way for a lockless fast path. Updates #35112. Change-Id: I32cfdcd8528fb7be985640e4f3a13cb98ffb7865 Reviewed-on: https://go-review.googlesource.com/c/go/+/196642 Run-TryBot: Michael Knyszek <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Austin Clements <[email protected]>
1 parent a762221 commit 4517c02

File tree

3 files changed

+106
-5
lines changed

3 files changed

+106
-5
lines changed

src/runtime/mheap.go

Lines changed: 87 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -973,6 +973,84 @@ func (h *mheap) allocNeedsZero(base, npage uintptr) (needZero bool) {
973973
return
974974
}
975975

976+
// tryAllocMSpan attempts to allocate an mspan object from
977+
// the P-local cache, but may fail.
978+
//
979+
// h need not be locked.
980+
//
981+
// This caller must ensure that its P won't change underneath
982+
// it during this function. Currently to ensure that we enforce
983+
// that the function is run on the system stack, because that's
984+
// the only place it is used now. In the future, this requirement
985+
// may be relaxed if its use is necessary elsewhere.
986+
//
987+
//go:systemstack
988+
func (h *mheap) tryAllocMSpan() *mspan {
989+
pp := getg().m.p.ptr()
990+
// If we don't have a p or the cache is empty, we can't do
991+
// anything here.
992+
if pp == nil || pp.mspancache.len == 0 {
993+
return nil
994+
}
995+
// Pull off the last entry in the cache.
996+
s := pp.mspancache.buf[pp.mspancache.len-1]
997+
pp.mspancache.len--
998+
return s
999+
}
1000+
1001+
// allocMSpanLocked allocates an mspan object.
1002+
//
1003+
// h must be locked.
1004+
//
1005+
// allocMSpanLocked must be called on the system stack because
1006+
// its caller holds the heap lock. See mheap for details.
1007+
// Running on the system stack also ensures that we won't
1008+
// switch Ps during this function. See tryAllocMSpan for details.
1009+
//
1010+
//go:systemstack
1011+
func (h *mheap) allocMSpanLocked() *mspan {
1012+
pp := getg().m.p.ptr()
1013+
if pp == nil {
1014+
// We don't have a p so just do the normal thing.
1015+
return (*mspan)(h.spanalloc.alloc())
1016+
}
1017+
// Refill the cache if necessary.
1018+
if pp.mspancache.len == 0 {
1019+
const refillCount = len(pp.mspancache.buf) / 2
1020+
for i := 0; i < refillCount; i++ {
1021+
pp.mspancache.buf[i] = (*mspan)(h.spanalloc.alloc())
1022+
}
1023+
pp.mspancache.len = refillCount
1024+
}
1025+
// Pull off the last entry in the cache.
1026+
s := pp.mspancache.buf[pp.mspancache.len-1]
1027+
pp.mspancache.len--
1028+
return s
1029+
}
1030+
1031+
// freeMSpanLocked free an mspan object.
1032+
//
1033+
// h must be locked.
1034+
//
1035+
// freeMSpanLocked must be called on the system stack because
1036+
// its caller holds the heap lock. See mheap for details.
1037+
// Running on the system stack also ensures that we won't
1038+
// switch Ps during this function. See tryAllocMSpan for details.
1039+
//
1040+
//go:systemstack
1041+
func (h *mheap) freeMSpanLocked(s *mspan) {
1042+
pp := getg().m.p.ptr()
1043+
// First try to free the mspan directly to the cache.
1044+
if pp != nil && pp.mspancache.len < len(pp.mspancache.buf) {
1045+
pp.mspancache.buf[pp.mspancache.len] = s
1046+
pp.mspancache.len++
1047+
return
1048+
}
1049+
// Failing that (or if we don't have a p), just free it to
1050+
// the heap.
1051+
h.spanalloc.free(unsafe.Pointer(s))
1052+
}
1053+
9761054
// allocSpan allocates an mspan which owns npages worth of memory.
9771055
//
9781056
// If manual == false, allocSpan allocates a heap span of class spanclass
@@ -995,6 +1073,9 @@ func (h *mheap) allocSpan(npages uintptr, manual bool, spanclass spanClass, sysS
9951073
gp := getg()
9961074
base, scav := uintptr(0), uintptr(0)
9971075

1076+
// Try to allocate a cached span.
1077+
s = h.tryAllocMSpan()
1078+
9981079
// We failed to do what we need to do without the lock.
9991080
lock(&h.lock)
10001081

@@ -1014,6 +1095,11 @@ func (h *mheap) allocSpan(npages uintptr, manual bool, spanclass spanClass, sysS
10141095
throw("grew heap, but no adequate free space found")
10151096

10161097
HaveBase:
1098+
if s == nil {
1099+
// We failed to get an mspan earlier, so grab
1100+
// one now that we have the heap lock.
1101+
s = h.allocMSpanLocked()
1102+
}
10171103
if !manual {
10181104
// This is a heap span, so we should do some additional accounting
10191105
// which may only be done with the heap locked.
@@ -1036,9 +1122,6 @@ HaveBase:
10361122
gcController.revise()
10371123
}
10381124
}
1039-
1040-
// Allocate an mspan object before releasing the lock.
1041-
s = (*mspan)(h.spanalloc.alloc())
10421125
unlock(&h.lock)
10431126

10441127
// Initialize the span.
@@ -1294,7 +1377,7 @@ func (h *mheap) freeSpanLocked(s *mspan, acctinuse, acctidle bool) {
12941377

12951378
// Free the span structure. We no longer have a use for it.
12961379
s.state.set(mSpanDead)
1297-
h.spanalloc.free(unsafe.Pointer(s))
1380+
h.freeMSpanLocked(s)
12981381
}
12991382

13001383
// scavengeAll visits each node in the free treap and scavenges the

src/runtime/proc.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4082,6 +4082,13 @@ func (pp *p) destroy() {
40824082
}
40834083
pp.deferpool[i] = pp.deferpoolbuf[i][:0]
40844084
}
4085+
systemstack(func() {
4086+
for i := 0; i < pp.mspancache.len; i++ {
4087+
// Safe to call since the world is stopped.
4088+
mheap_.spanalloc.free(unsafe.Pointer(pp.mspancache.buf[i]))
4089+
}
4090+
pp.mspancache.len = 0
4091+
})
40854092
freemcache(pp.mcache)
40864093
pp.mcache = nil
40874094
gfpurge(pp)

src/runtime/runtime2.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,17 @@ type p struct {
588588
sudogcache []*sudog
589589
sudogbuf [128]*sudog
590590

591+
// Cache of mspan objects from the heap.
592+
mspancache struct {
593+
// We need an explicit length here because this field is used
594+
// in allocation codepaths where write barriers are not allowed,
595+
// and eliminating the write barrier/keeping it eliminated from
596+
// slice updates is tricky, moreso than just managing the length
597+
// ourselves.
598+
len int
599+
buf [128]*mspan
600+
}
601+
591602
tracebuf traceBufPtr
592603

593604
// traceSweep indicates the sweep events should be traced.
@@ -600,7 +611,7 @@ type p struct {
600611

601612
palloc persistentAlloc // per-P to avoid mutex
602613

603-
_ uint32 // Alignment for atomic fields below
614+
// _ uint32 // Alignment for atomic fields below
604615

605616
// Per-P GC state
606617
gcAssistTime int64 // Nanoseconds in assistAlloc

0 commit comments

Comments
 (0)