Skip to content

Commit b46bf02

Browse files
committed
runtime: separate scavenged spans
This change adds a new treap to mheap which contains scavenged (i.e. its physical pages were returned to the OS) spans. As of this change, spans may no longer be partially scavenged. For #14045. Change-Id: I0d428a255c6d3f710b9214b378f841b997df0993 Reviewed-on: https://go-review.googlesource.com/c/139298 Run-TryBot: Michael Knyszek <mknyszek@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Austin Clements <austin@google.com>
1 parent 239341f commit b46bf02

File tree

3 files changed

+103
-54
lines changed

3 files changed

+103
-54
lines changed

src/runtime/mgclarge.go

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,6 @@ func (root *mTreap) removeNode(t *treapNode) {
211211
if t.spanKey.npages != t.npagesKey {
212212
throw("span and treap node npages do not match")
213213
}
214-
215214
// Rotate t down to be leaf of tree for removal, respecting priorities.
216215
for t.right != nil || t.left != nil {
217216
if t.right == nil || t.left != nil && t.left.priority < t.right.priority {
@@ -281,17 +280,6 @@ func (root *mTreap) removeSpan(span *mspan) {
281280
root.removeNode(t)
282281
}
283282

284-
// scavengetreap visits each node in the treap and scavenges the
285-
// treapNode's span.
286-
func scavengetreap(treap *treapNode, now, limit uint64) uintptr {
287-
if treap == nil {
288-
return 0
289-
}
290-
return scavengeTreapNode(treap, now, limit) +
291-
scavengetreap(treap.left, now, limit) +
292-
scavengetreap(treap.right, now, limit)
293-
}
294-
295283
// rotateLeft rotates the tree rooted at node x.
296284
// turning (x a (y b c)) into (y (x a b) c).
297285
func (root *mTreap) rotateLeft(x *treapNode) {

src/runtime/mheap.go

Lines changed: 103 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ const minPhysPageSize = 4096
3030
//go:notinheap
3131
type mheap struct {
3232
lock mutex
33-
free mTreap // free treap of spans
33+
free mTreap // free and non-scavenged spans
34+
scav mTreap // free and scavenged spans
3435
busy mSpanList // busy list of spans
3536
sweepgen uint32 // sweep generation, see comment in mspan
3637
sweepdone uint32 // all spans are swept
@@ -60,7 +61,7 @@ type mheap struct {
6061
// on the swept stack.
6162
sweepSpans [2]gcSweepBuf
6263

63-
//_ uint32 // align uint64 fields on 32-bit for atomics
64+
_ uint32 // align uint64 fields on 32-bit for atomics
6465

6566
// Proportional sweep
6667
//
@@ -132,7 +133,7 @@ type mheap struct {
132133
// (the actual arenas). This is only used on 32-bit.
133134
arena linearAlloc
134135

135-
//_ uint32 // ensure 64-bit alignment of central
136+
// _ uint32 // ensure 64-bit alignment of central
136137

137138
// central free lists for small size classes.
138139
// the padding makes sure that the MCentrals are
@@ -840,18 +841,31 @@ func (h *mheap) setSpans(base, npage uintptr, s *mspan) {
840841
func (h *mheap) allocSpanLocked(npage uintptr, stat *uint64) *mspan {
841842
var s *mspan
842843

843-
// Best fit in the treap of spans.
844+
// First, attempt to allocate from free spans, then from
845+
// scavenged spans, looking for best fit in each.
844846
s = h.free.remove(npage)
845-
if s == nil {
846-
if !h.grow(npage) {
847-
return nil
848-
}
849-
s = h.free.remove(npage)
850-
if s == nil {
851-
return nil
852-
}
847+
if s != nil {
848+
goto HaveSpan
849+
}
850+
s = h.scav.remove(npage)
851+
if s != nil {
852+
goto HaveSpan
853+
}
854+
// On failure, grow the heap and try again.
855+
if !h.grow(npage) {
856+
return nil
857+
}
858+
s = h.free.remove(npage)
859+
if s != nil {
860+
goto HaveSpan
861+
}
862+
s = h.scav.remove(npage)
863+
if s != nil {
864+
goto HaveSpan
853865
}
866+
return nil
854867

868+
HaveSpan:
855869
// Mark span in use.
856870
if s.state != mSpanFree {
857871
throw("MHeap_AllocLocked - MSpan not free")
@@ -1002,46 +1016,107 @@ func (h *mheap) freeSpanLocked(s *mspan, acctinuse, acctidle bool, unusedsince i
10021016
if unusedsince == 0 {
10031017
s.unusedsince = nanotime()
10041018
}
1005-
s.npreleased = 0
1019+
1020+
// We scavenge s at the end after coalescing if s or anything
1021+
// it merged with is marked scavenged.
1022+
needsScavenge := s.npreleased != 0
1023+
prescavenged := s.npreleased * pageSize // number of bytes already scavenged.
10061024

10071025
// Coalesce with earlier, later spans.
10081026
if before := spanOf(s.base() - 1); before != nil && before.state == mSpanFree {
10091027
// Now adjust s.
10101028
s.startAddr = before.startAddr
10111029
s.npages += before.npages
1012-
s.npreleased = before.npreleased // absorb released pages
10131030
s.needzero |= before.needzero
10141031
h.setSpan(before.base(), s)
1032+
s.npreleased += before.npreleased // absorb released pages
10151033
// The size is potentially changing so the treap needs to delete adjacent nodes and
10161034
// insert back as a combined node.
1017-
h.free.removeSpan(before)
1035+
if before.npreleased == 0 {
1036+
h.free.removeSpan(before)
1037+
} else {
1038+
h.scav.removeSpan(before)
1039+
needsScavenge = true
1040+
prescavenged += before.npreleased * pageSize
1041+
}
10181042
before.state = mSpanDead
10191043
h.spanalloc.free(unsafe.Pointer(before))
10201044
}
10211045

10221046
// Now check to see if next (greater addresses) span is free and can be coalesced.
10231047
if after := spanOf(s.base() + s.npages*pageSize); after != nil && after.state == mSpanFree {
10241048
s.npages += after.npages
1025-
s.npreleased += after.npreleased
10261049
s.needzero |= after.needzero
10271050
h.setSpan(s.base()+s.npages*pageSize-1, s)
1028-
h.free.removeSpan(after)
1051+
if after.npreleased == 0 {
1052+
h.free.removeSpan(after)
1053+
} else {
1054+
h.scav.removeSpan(after)
1055+
needsScavenge = true
1056+
prescavenged += after.npreleased * pageSize
1057+
}
1058+
s.npreleased += after.npreleased
10291059
after.state = mSpanDead
10301060
h.spanalloc.free(unsafe.Pointer(after))
10311061
}
10321062

1033-
// Insert s into the free treap.
1034-
h.free.insert(s)
1063+
if needsScavenge {
1064+
// When coalescing spans, some physical pages which
1065+
// were not returned to the OS previously because
1066+
// they were only partially covered by the span suddenly
1067+
// become available for scavenging. We want to make sure
1068+
// those holes are filled in, and the span is properly
1069+
// scavenged. Rather than trying to detect those holes
1070+
// directly, we collect how many bytes were already
1071+
// scavenged above and subtract that from heap_released
1072+
// before re-scavenging the entire newly-coalesced span,
1073+
// which will implicitly bump up heap_released.
1074+
memstats.heap_released -= uint64(prescavenged)
1075+
s.scavenge()
1076+
}
1077+
1078+
// Insert s into the appropriate treap.
1079+
if s.npreleased != 0 {
1080+
h.scav.insert(s)
1081+
} else {
1082+
h.free.insert(s)
1083+
}
10351084
}
10361085

1037-
func scavengeTreapNode(t *treapNode, now, limit uint64) uintptr {
1038-
s := t.spanKey
1039-
if (now-uint64(s.unusedsince)) > limit && s.npreleased != s.npages {
1040-
if released := s.scavenge(); released != 0 {
1041-
return released
1086+
// scavengeAll visits each node in the unscav treap and scavenges the
1087+
// treapNode's span. It then removes the scavenged span from
1088+
// unscav and adds it into scav before continuing. h must be locked.
1089+
func (h *mheap) scavengeAll(now, limit uint64) uintptr {
1090+
// Compute the left-most child in unscav to start iteration from.
1091+
t := h.free.treap
1092+
if t == nil {
1093+
return 0
1094+
}
1095+
for t.left != nil {
1096+
t = t.left
1097+
}
1098+
// Iterate over the treap be computing t's successor before
1099+
// potentially scavenging it.
1100+
released := uintptr(0)
1101+
for t != nil {
1102+
s := t.spanKey
1103+
next := t.succ()
1104+
if (now-uint64(s.unusedsince)) > limit {
1105+
r := s.scavenge()
1106+
if r != 0 {
1107+
// If we ended up scavenging s, then remove it from unscav
1108+
// and add it to scav. This is safe to do since we've already
1109+
// moved to t's successor.
1110+
h.free.removeNode(t)
1111+
h.scav.insert(s)
1112+
released += r
1113+
}
10421114
}
1115+
// Move t forward to its successor to iterate over the whole
1116+
// treap.
1117+
t = next
10431118
}
1044-
return 0
1119+
return released
10451120
}
10461121

10471122
func (h *mheap) scavenge(k int32, now, limit uint64) {
@@ -1051,13 +1126,13 @@ func (h *mheap) scavenge(k int32, now, limit uint64) {
10511126
gp := getg()
10521127
gp.m.mallocing++
10531128
lock(&h.lock)
1054-
sumreleased := scavengetreap(h.free.treap, now, limit)
1129+
released := h.scavengeAll(now, limit)
10551130
unlock(&h.lock)
10561131
gp.m.mallocing--
10571132

10581133
if debug.gctrace > 0 {
1059-
if sumreleased > 0 {
1060-
print("scvg", k, ": ", sumreleased>>20, " MB released\n")
1134+
if released > 0 {
1135+
print("scvg", k, ": ", released>>20, " MB released\n")
10611136
}
10621137
print("scvg", k, ": inuse: ", memstats.heap_inuse>>20, ", idle: ", memstats.heap_idle>>20, ", sys: ", memstats.heap_sys>>20, ", released: ", memstats.heap_released>>20, ", consumed: ", (memstats.heap_sys-memstats.heap_released)>>20, " (MB)\n")
10631138
}

src/runtime/mstats.go

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -42,20 +42,6 @@ type mstats struct {
4242
heap_released uint64 // bytes released to the os
4343
heap_objects uint64 // total number of allocated objects
4444

45-
// TODO(austin): heap_released is both useless and inaccurate
46-
// in its current form. It's useless because, from the user's
47-
// and OS's perspectives, there's no difference between a page
48-
// that has not yet been faulted in and a page that has been
49-
// released back to the OS. We could fix this by considering
50-
// newly mapped spans to be "released". It's inaccurate
51-
// because when we split a large span for allocation, we
52-
// "unrelease" all pages in the large span and not just the
53-
// ones we split off for use. This is trickier to fix because
54-
// we currently don't know which pages of a span we've
55-
// released. We could fix it by separating "free" and
56-
// "released" spans, but then we have to allocate from runs of
57-
// free and released spans.
58-
5945
// Statistics about allocation of low-level fixed-size structures.
6046
// Protected by FixAlloc locks.
6147
stacks_inuse uint64 // bytes in manually-managed stack spans

0 commit comments

Comments
 (0)