Skip to content

Commit 9baa430

Browse files
committed
runtime: merge all treaps into one implementation
This change modifies the treap implementation to support holding all spans in a single treap, instead of keeping them all in separate treaps. This improves ergonomics for nearly all treap-related callsites. With that said, iteration is now more expensive, but it never occurs on the fast path, only on scavenging-related paths. This change opens up the opportunity for further optimizations, such as splitting spans without treap removal (taking treap removal off the span allocator's critical path) as well as improvements to treap iteration (building linked lists for each iteration type and managing them on insert/removal, since those operations should be less frequent). For #30333. Change-Id: I3dac97afd3682a37fda09ae8656a770e1369d0a9 Reviewed-on: https://go-review.googlesource.com/c/go/+/174398 Run-TryBot: Michael Knyszek <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Austin Clements <[email protected]>
1 parent 1ad2298 commit 9baa430

File tree

4 files changed

+210
-134
lines changed

4 files changed

+210
-134
lines changed

src/runtime/export_test.go

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ func ReadMemStatsSlow() (base, slow MemStats) {
339339
slow.BySize[i].Frees = bySize[i].Frees
340340
}
341341

342-
for i := mheap_.scav.start(); i.valid(); i = i.next() {
342+
for i := mheap_.free.start(0, 0); i.valid(); i = i.next() {
343343
slow.HeapReleased += uint64(i.span().released())
344344
}
345345

@@ -522,11 +522,12 @@ type Span struct {
522522
*mspan
523523
}
524524

525-
func AllocSpan(base, npages uintptr) Span {
525+
func AllocSpan(base, npages uintptr, scavenged bool) Span {
526526
lock(&mheap_.lock)
527527
s := (*mspan)(mheap_.spanalloc.alloc())
528528
unlock(&mheap_.lock)
529529
s.init(base, npages)
530+
s.scavenged = scavenged
530531
return Span{s}
531532
}
532533

@@ -545,6 +546,17 @@ func (s Span) Pages() uintptr {
545546
return s.mspan.npages
546547
}
547548

549+
type TreapIterType int
550+
551+
const (
552+
TreapIterScav TreapIterType = TreapIterType(treapIterScav)
553+
TreapIterBits = treapIterBits
554+
)
555+
556+
func (s Span) MatchesIter(mask, match TreapIterType) bool {
557+
return s.mspan.matchesIter(treapIterType(mask), treapIterType(match))
558+
}
559+
548560
type TreapIter struct {
549561
treapIter
550562
}
@@ -575,12 +587,12 @@ type Treap struct {
575587
mTreap
576588
}
577589

578-
func (t *Treap) Start() TreapIter {
579-
return TreapIter{t.start()}
590+
func (t *Treap) Start(mask, match TreapIterType) TreapIter {
591+
return TreapIter{t.start(treapIterType(mask), treapIterType(match))}
580592
}
581593

582-
func (t *Treap) End() TreapIter {
583-
return TreapIter{t.end()}
594+
func (t *Treap) End(mask, match TreapIterType) TreapIter {
595+
return TreapIter{t.end(treapIterType(mask), treapIterType(match))}
584596
}
585597

586598
func (t *Treap) Insert(s Span) {

src/runtime/mgclarge.go

Lines changed: 60 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -185,14 +185,50 @@ func (t *treapNode) validateMaxPages() uintptr {
185185
return max
186186
}
187187

188+
// treapIterType represents the type of iteration to perform
189+
// over the treap. Each choice effectively represents a filter,
190+
// i.e. spans that do not satisfy the conditions of the iteration
191+
// type will be skipped over.
192+
type treapIterType uint8
193+
194+
const (
195+
treapIterScav treapIterType = 1 << iota // scavenged spans
196+
treapIterBits = iota
197+
)
198+
199+
// matches returns true if t satisfies the filter given by mask and match. mask
200+
// is a bit-set of span properties to filter on.
201+
//
202+
// In other words, matches returns true if all properties set in mask have the
203+
// value given by the corresponding bits in match.
204+
func (t treapIterType) matches(mask, match treapIterType) bool {
205+
return t&mask == match
206+
}
207+
208+
// iterType returns the treapIterType associated with this span.
209+
func (s *mspan) iterType() treapIterType {
210+
have := treapIterType(0)
211+
if s.scavenged {
212+
have |= treapIterScav
213+
}
214+
return have
215+
}
216+
217+
// matchesIter is a convenience method which checks if a span
218+
// meets the criteria of the mask and match for an iter type.
219+
func (s *mspan) matchesIter(mask, match treapIterType) bool {
220+
return s.iterType().matches(mask, match)
221+
}
222+
188223
// treapIter is a bidirectional iterator type which may be used to iterate over a
189224
// an mTreap in-order forwards (increasing order) or backwards (decreasing order).
190225
// Its purpose is to hide details about the treap from users when trying to iterate
191226
// over it.
192227
//
193228
// To create iterators over the treap, call start or end on an mTreap.
194229
type treapIter struct {
195-
t *treapNode
230+
mask, match treapIterType
231+
t *treapNode
196232
}
197233

198234
// span returns the span at the current position in the treap.
@@ -211,40 +247,52 @@ func (i *treapIter) valid() bool {
211247
// ceases to be valid, calling next will panic.
212248
func (i treapIter) next() treapIter {
213249
i.t = i.t.succ()
250+
for i.valid() && !i.span().matchesIter(i.mask, i.match) {
251+
i.t = i.t.succ()
252+
}
214253
return i
215254
}
216255

217256
// prev moves the iterator backwards by one. Once the iterator
218257
// ceases to be valid, calling prev will panic.
219258
func (i treapIter) prev() treapIter {
220259
i.t = i.t.pred()
260+
for i.valid() && !i.span().matchesIter(i.mask, i.match) {
261+
i.t = i.t.pred()
262+
}
221263
return i
222264
}
223265

224266
// start returns an iterator which points to the start of the treap (the
225-
// left-most node in the treap).
226-
func (root *mTreap) start() treapIter {
267+
// left-most node in the treap) subject to mask and match constraints.
268+
func (root *mTreap) start(mask, match treapIterType) treapIter {
227269
t := root.treap
228270
if t == nil {
229271
return treapIter{}
230272
}
231273
for t.left != nil {
232274
t = t.left
233275
}
234-
return treapIter{t: t}
276+
for t != nil && !t.span.matchesIter(mask, match) {
277+
t = t.succ()
278+
}
279+
return treapIter{mask, match, t}
235280
}
236281

237282
// end returns an iterator which points to the end of the treap (the
238-
// right-most node in the treap).
239-
func (root *mTreap) end() treapIter {
283+
// right-most node in the treap) subject to mask and match constraints.
284+
func (root *mTreap) end(mask, match treapIterType) treapIter {
240285
t := root.treap
241286
if t == nil {
242287
return treapIter{}
243288
}
244289
for t.right != nil {
245290
t = t.right
246291
}
247-
return treapIter{t: t}
292+
for t != nil && !t.span.matchesIter(mask, match) {
293+
t = t.pred()
294+
}
295+
return treapIter{mask, match, t}
248296
}
249297

250298
// insert adds span to the large span treap.
@@ -348,9 +396,10 @@ func (root *mTreap) removeNode(t *treapNode) {
348396
mheap_.treapalloc.free(unsafe.Pointer(t))
349397
}
350398

351-
// find searches for, finds, and returns the treap iterator representing the
352-
// position of the span with the smallest base address which is at least npages
353-
// in size. If no span has at least npages it returns an invalid iterator.
399+
// find searches for, finds, and returns the treap iterator over all spans
400+
// representing the position of the span with the smallest base address which is
401+
// at least npages in size. If no span has at least npages it returns an invalid
402+
// iterator.
354403
//
355404
// This algorithm is as follows:
356405
// * If there's a left child and its subtree can satisfy this allocation,
@@ -391,7 +440,7 @@ func (root *mTreap) find(npages uintptr) treapIter {
391440
t = nil
392441
}
393442
}
394-
return treapIter{t}
443+
return treapIter{t: t}
395444
}
396445

397446
// removeSpan searches for, finds, deletes span along with

src/runtime/mheap.go

Lines changed: 24 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,7 @@ const minPhysPageSize = 4096
3030
//go:notinheap
3131
type mheap struct {
3232
lock mutex
33-
free mTreap // free and non-scavenged spans
34-
scav mTreap // free and scavenged spans
33+
free mTreap // free spans
3534
sweepgen uint32 // sweep generation, see comment in mspan
3635
sweepdone uint32 // all spans are swept
3736
sweepers uint32 // number of active sweepone calls
@@ -60,7 +59,7 @@ type mheap struct {
6059
// on the swept stack.
6160
sweepSpans [2]gcSweepBuf
6261

63-
_ uint32 // align uint64 fields on 32-bit for atomics
62+
// _ uint32 // align uint64 fields on 32-bit for atomics
6463

6564
// Proportional sweep
6665
//
@@ -464,7 +463,7 @@ func (h *mheap) coalesce(s *mspan) {
464463

465464
// The size is potentially changing so the treap needs to delete adjacent nodes and
466465
// insert back as a combined node.
467-
h.treapForSpan(other).removeSpan(other)
466+
h.free.removeSpan(other)
468467
other.state = mSpanDead
469468
h.spanalloc.free(unsafe.Pointer(other))
470469
}
@@ -482,7 +481,7 @@ func (h *mheap) coalesce(s *mspan) {
482481
return
483482
}
484483
// Since we're resizing other, we must remove it from the treap.
485-
h.treapForSpan(other).removeSpan(other)
484+
h.free.removeSpan(other)
486485

487486
// Round boundary to the nearest physical page size, toward the
488487
// scavenged span.
@@ -500,7 +499,7 @@ func (h *mheap) coalesce(s *mspan) {
500499
h.setSpan(boundary, b)
501500

502501
// Re-insert other now that it has a new size.
503-
h.treapForSpan(other).insert(other)
502+
h.free.insert(other)
504503
}
505504

506505
// Coalesce with earlier, later spans.
@@ -1101,57 +1100,27 @@ func (h *mheap) setSpans(base, npage uintptr, s *mspan) {
11011100
}
11021101
}
11031102

1104-
// treapForSpan returns the appropriate treap for a span for
1105-
// insertion and removal.
1106-
func (h *mheap) treapForSpan(span *mspan) *mTreap {
1107-
if span.scavenged {
1108-
return &h.scav
1109-
}
1110-
return &h.free
1111-
}
1112-
1113-
// pickFreeSpan acquires a free span from internal free list
1114-
// structures if one is available. Otherwise returns nil.
1115-
// h must be locked.
1116-
func (h *mheap) pickFreeSpan(npage uintptr) *mspan {
1117-
tf := h.free.find(npage)
1118-
ts := h.scav.find(npage)
1119-
1120-
// Check for whichever treap gave us the smaller, non-nil result.
1121-
// Note that we want the _smaller_ free span, i.e. the free span
1122-
// closer in size to the amount we requested (npage).
1123-
var s *mspan
1124-
if tf.valid() && (!ts.valid() || tf.span().base() <= ts.span().base()) {
1125-
s = tf.span()
1126-
h.free.erase(tf)
1127-
} else if ts.valid() && (!tf.valid() || tf.span().base() > ts.span().base()) {
1128-
s = ts.span()
1129-
h.scav.erase(ts)
1130-
}
1131-
return s
1132-
}
1133-
11341103
// Allocates a span of the given size. h must be locked.
11351104
// The returned span has been removed from the
11361105
// free structures, but its state is still mSpanFree.
11371106
func (h *mheap) allocSpanLocked(npage uintptr, stat *uint64) *mspan {
1138-
var s *mspan
1139-
1140-
s = h.pickFreeSpan(npage)
1141-
if s != nil {
1107+
t := h.free.find(npage)
1108+
if t.valid() {
11421109
goto HaveSpan
11431110
}
1144-
// On failure, grow the heap and try again.
11451111
if !h.grow(npage) {
11461112
return nil
11471113
}
1148-
s = h.pickFreeSpan(npage)
1149-
if s != nil {
1114+
t = h.free.find(npage)
1115+
if t.valid() {
11501116
goto HaveSpan
11511117
}
11521118
throw("grew heap, but no adequate free span found")
11531119

11541120
HaveSpan:
1121+
s := t.span()
1122+
h.free.erase(t)
1123+
11551124
// Mark span in use.
11561125
if s.state != mSpanFree {
11571126
throw("candidate mspan for allocation is not free")
@@ -1339,8 +1308,8 @@ func (h *mheap) freeSpanLocked(s *mspan, acctinuse, acctidle bool, unusedsince i
13391308
// Coalesce span with neighbors.
13401309
h.coalesce(s)
13411310

1342-
// Insert s into the appropriate treap.
1343-
h.treapForSpan(s).insert(s)
1311+
// Insert s into the treap.
1312+
h.free.insert(s)
13441313
}
13451314

13461315
// scavengeLocked scavenges nbytes worth of spans in the free treap by
@@ -1355,10 +1324,11 @@ func (h *mheap) scavengeLocked(nbytes uintptr) {
13551324
h.scavengeCredit -= nbytes
13561325
return
13571326
}
1358-
// Iterate over the treap backwards (from highest address to lowest address)
1359-
// scavenging spans until we've reached our quota of nbytes.
1327+
// Iterate over the unscavenged spans in the treap backwards (from highest
1328+
// address to lowest address) scavenging spans until we've reached our
1329+
// quota of nbytes.
13601330
released := uintptr(0)
1361-
for t := h.free.end(); released < nbytes && t.valid(); {
1331+
for t := h.free.end(treapIterScav, 0); released < nbytes && t.valid(); {
13621332
s := t.span()
13631333
r := s.scavenge()
13641334
if r == 0 {
@@ -1373,7 +1343,7 @@ func (h *mheap) scavengeLocked(nbytes uintptr) {
13731343
// the same scavenged state adjacent to each other.
13741344
h.coalesce(s)
13751345
t = n
1376-
h.scav.insert(s)
1346+
h.free.insert(s)
13771347
released += r
13781348
}
13791349
// If we over-scavenged, turn that extra amount into credit.
@@ -1386,20 +1356,19 @@ func (h *mheap) scavengeLocked(nbytes uintptr) {
13861356
// treapNode's span. It then removes the scavenged span from
13871357
// unscav and adds it into scav before continuing. h must be locked.
13881358
func (h *mheap) scavengeAllLocked(now, limit uint64) uintptr {
1389-
// Iterate over the treap scavenging spans if unused for at least limit time.
1359+
// Iterate over the unscavenged spans in the treap scavenging spans
1360+
// if unused for at least limit time.
13901361
released := uintptr(0)
1391-
for t := h.free.start(); t.valid(); {
1362+
for t := h.free.start(treapIterScav, 0); t.valid(); {
13921363
s := t.span()
13931364
n := t.next()
13941365
if (now - uint64(s.unusedsince)) > limit {
13951366
r := s.scavenge()
13961367
if r != 0 {
13971368
h.free.erase(t)
1398-
// Now that s is scavenged, we must eagerly coalesce it
1399-
// with its neighbors to prevent having two spans with
1400-
// the same scavenged state adjacent to each other.
1369+
// See (*mheap).scavenge.
14011370
h.coalesce(s)
1402-
h.scav.insert(s)
1371+
h.free.insert(s)
14031372
released += r
14041373
}
14051374
}

0 commit comments

Comments
 (0)