Skip to content

Commit c803ffc

Browse files
committed
runtime: scavenge large spans before heap growth
This change scavenges the largest spans before growing the heap for physical pages to "make up" for the newly-mapped space which, presumably, will be touched. In theory, this approach to scavenging helps reduce the RSS of an application by marking fragments in memory as reclaimable to the OS more eagerly than before. In practice this may not necessarily be true, depending on how sysUnused is implemented for each platform. Fixes #14045. Change-Id: Iab60790be05935865fc71f793cb9323ab00a18bd Reviewed-on: https://go-review.googlesource.com/c/139719 Run-TryBot: Michael Knyszek <mknyszek@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Austin Clements <austin@google.com>
1 parent db82a1b commit c803ffc

File tree

1 file changed

+48
-0
lines changed

1 file changed

+48
-0
lines changed

src/runtime/mheap.go

+48
Original file line numberDiff line numberDiff line change
@@ -945,6 +945,14 @@ func (h *mheap) grow(npage uintptr) bool {
945945
return false
946946
}
947947

948+
// Scavenge some pages out of the free treap to make up for
949+
// the virtual memory space we just allocated. We prefer to
950+
// scavenge the largest spans first since the cost of scavenging
951+
// is proportional to the number of sysUnused() calls rather than
952+
// the number of pages released, so we make fewer of those calls
953+
// with larger spans.
954+
h.scavengeLargest(size)
955+
948956
// Create a fake "in use" span and free it, so that the
949957
// right coalescing happens.
950958
s := (*mspan)(h.spanalloc.alloc())
@@ -1107,6 +1115,46 @@ func (h *mheap) freeSpanLocked(s *mspan, acctinuse, acctidle bool, unusedsince i
11071115
}
11081116
}
11091117

1118+
// scavengeLargest scavenges nbytes worth of spans in unscav
1119+
// starting from the largest span and working down. It then takes those spans
1120+
// and places them in scav. h must be locked.
1121+
func (h *mheap) scavengeLargest(nbytes uintptr) {
1122+
// Find the largest child.
1123+
t := h.free.treap
1124+
if t == nil {
1125+
return
1126+
}
1127+
for t.right != nil {
1128+
t = t.right
1129+
}
1130+
// Iterate over the treap from the largest child to the smallest by
1131+
// starting from the largest and finding its predecessor until we've
1132+
// recovered nbytes worth of physical memory, or it no longer has a
1133+
// predecessor (meaning the treap is now empty).
1134+
released := uintptr(0)
1135+
for t != nil && released < nbytes {
1136+
s := t.spanKey
1137+
r := s.scavenge()
1138+
if r == 0 {
1139+
// Since we're going in order of largest-to-smallest span, this
1140+
// means all other spans are no bigger than s. There's a high
1141+
// chance that the other spans don't even cover a full page,
1142+
// (though they could) but iterating further just for a handful
1143+
// of pages probably isn't worth it, so just stop here.
1144+
//
1145+
// This check also preserves the invariant that spans that have
1146+
// `scavenged` set are only ever in the `scav` treap, and
1147+
// those which have it unset are only in the `free` treap.
1148+
return
1149+
}
1150+
prev := t.pred()
1151+
h.free.removeNode(t)
1152+
t = prev
1153+
h.scav.insert(s)
1154+
released += r
1155+
}
1156+
}
1157+
11101158
// scavengeAll visits each node in the unscav treap and scavenges the
11111159
// treapNode's span. It then removes the scavenged span from
11121160
// unscav and adds it into scav before continuing. h must be locked.

0 commit comments

Comments
 (0)