Skip to content

Commit 6e9ec14

Browse files
committed
runtime: redo insert/remove of large spans
Currently for spans with up to 1 MBytes (128 pages) we maintain an array indexed by the number of pages in the span. This is efficient both in terms of space as well as time to insert or remove a span of a particular size. Unfortunately for spans larger than 1 MByte we currently place them on a separate linked list. This results in O(n) behavior. Now that we are seeing heaps approaching 100 GBytes n is large enough to be noticed in real programs. This change replaces the linked list now used with a balanced binary tree structure called a treap. A treap is a probabilistically balanced tree offering O(logN) behavior for inserting and removing spans. To verify that this approach will work we start with noting that only spans with sizes > 1MByte will be put into the treap. This means that to support 1 TByte a treap will need at most 1 million nodes and can ideally be held in a treap with a depth of 20. Experiments with adding and removing randomly sized spans from the treap seem to result in treaps with depths of about twice the ideal or 40. A petabyte would require a tree of only twice again that depth again so this algorithm should last well into the future. Fixes #19393 Go1 benchmarks indicate this is basically an overall wash. Tue Mar 28 21:29:21 EDT 2017 name old time/op new time/op delta BinaryTree17-4 2.42s ± 1% 2.42s ± 1% ~ (p=0.980 n=21+21) Fannkuch11-4 3.00s ± 1% 3.18s ± 4% +6.10% (p=0.000 n=22+24) FmtFprintfEmpty-4 40.5ns ± 1% 40.3ns ± 3% ~ (p=0.692 n=22+25) FmtFprintfString-4 65.9ns ± 3% 64.6ns ± 1% -1.98% (p=0.000 n=24+23) FmtFprintfInt-4 69.6ns ± 1% 68.0ns ± 7% -2.30% (p=0.001 n=21+22) FmtFprintfIntInt-4 102ns ± 2% 99ns ± 1% -3.07% (p=0.000 n=23+23) FmtFprintfPrefixedInt-4 126ns ± 0% 125ns ± 0% -0.79% (p=0.000 n=19+17) FmtFprintfFloat-4 206ns ± 2% 205ns ± 1% ~ (p=0.671 n=23+21) FmtManyArgs-4 441ns ± 1% 445ns ± 1% +0.88% (p=0.000 n=22+23) GobDecode-4 5.73ms ± 1% 5.86ms ± 1% +2.37% (p=0.000 n=23+22) GobEncode-4 4.51ms ± 1% 4.89ms ± 1% +8.32% (p=0.000 n=22+22) Gzip-4 197ms ± 0% 202ms ± 1% +2.75% (p=0.000 n=23+24) Gunzip-4 32.9ms ± 8% 32.7ms ± 2% ~ (p=0.466 n=23+24) HTTPClientServer-4 57.3µs ± 1% 56.7µs ± 1% -0.94% (p=0.000 n=21+22) JSONEncode-4 13.8ms ± 1% 13.9ms ± 2% +1.14% (p=0.000 n=22+23) JSONDecode-4 47.4ms ± 1% 48.1ms ± 1% +1.49% (p=0.000 n=23+23) Mandelbrot200-4 3.92ms ± 0% 3.92ms ± 1% +0.21% (p=0.000 n=22+22) GoParse-4 2.89ms ± 1% 2.87ms ± 1% -0.68% (p=0.000 n=21+22) RegexpMatchEasy0_32-4 73.6ns ± 1% 72.0ns ± 2% -2.15% (p=0.000 n=21+22) RegexpMatchEasy0_1K-4 173ns ± 1% 173ns ± 1% ~ (p=0.847 n=22+24) RegexpMatchEasy1_32-4 71.9ns ± 1% 69.8ns ± 1% -2.99% (p=0.000 n=23+20) RegexpMatchEasy1_1K-4 314ns ± 1% 308ns ± 1% -1.91% (p=0.000 n=22+23) RegexpMatchMedium_32-4 106ns ± 0% 105ns ± 1% -0.58% (p=0.000 n=19+21) RegexpMatchMedium_1K-4 34.3µs ± 1% 34.3µs ± 1% ~ (p=0.871 n=23+22) RegexpMatchHard_32-4 1.67µs ± 1% 1.67µs ± 7% ~ (p=0.224 n=22+23) RegexpMatchHard_1K-4 51.5µs ± 1% 50.4µs ± 1% -1.99% (p=0.000 n=22+23) Revcomp-4 383ms ± 1% 415ms ± 0% +8.51% (p=0.000 n=22+22) Template-4 51.5ms ± 1% 51.5ms ± 1% ~ (p=0.555 n=20+23) TimeParse-4 279ns ± 2% 277ns ± 1% -0.95% (p=0.000 n=24+22) TimeFormat-4 294ns ± 1% 296ns ± 1% +0.58% (p=0.003 n=24+23) [Geo mean] 43.7µs 43.8µs +0.32% name old speed new speed delta GobDecode-4 134MB/s ± 1% 131MB/s ± 1% -2.32% (p=0.000 n=23+22) GobEncode-4 170MB/s ± 1% 157MB/s ± 1% -7.68% (p=0.000 n=22+22) Gzip-4 98.7MB/s ± 0% 96.1MB/s ± 1% -2.68% (p=0.000 n=23+24) Gunzip-4 590MB/s ± 7% 593MB/s ± 2% ~ (p=0.466 n=23+24) JSONEncode-4 141MB/s ± 1% 139MB/s ± 2% -1.13% (p=0.000 n=22+23) JSONDecode-4 40.9MB/s ± 1% 40.3MB/s ± 0% -1.47% (p=0.000 n=23+23) GoParse-4 20.1MB/s ± 1% 20.2MB/s ± 1% +0.69% (p=0.000 n=21+22) RegexpMatchEasy0_32-4 435MB/s ± 1% 444MB/s ± 2% +2.21% (p=0.000 n=21+22) RegexpMatchEasy0_1K-4 5.89GB/s ± 1% 5.89GB/s ± 1% ~ (p=0.439 n=22+24) RegexpMatchEasy1_32-4 445MB/s ± 1% 459MB/s ± 1% +3.06% (p=0.000 n=23+20) RegexpMatchEasy1_1K-4 3.26GB/s ± 1% 3.32GB/s ± 1% +1.97% (p=0.000 n=22+23) RegexpMatchMedium_32-4 9.40MB/s ± 1% 9.44MB/s ± 1% +0.43% (p=0.000 n=23+21) RegexpMatchMedium_1K-4 29.8MB/s ± 1% 29.8MB/s ± 1% ~ (p=0.826 n=23+22) RegexpMatchHard_32-4 19.1MB/s ± 1% 19.1MB/s ± 7% ~ (p=0.233 n=22+23) RegexpMatchHard_1K-4 19.9MB/s ± 1% 20.3MB/s ± 1% +2.03% (p=0.000 n=22+23) Revcomp-4 664MB/s ± 1% 612MB/s ± 0% -7.85% (p=0.000 n=22+22) Template-4 37.6MB/s ± 1% 37.7MB/s ± 1% ~ (p=0.558 n=20+23) [Geo mean] 134MB/s 133MB/s -0.76% Tue Mar 28 22:16:54 EDT 2017 Change-Id: I4a4f5c2b53d3fb85ef76c98522d3ed5cf8ae5b7e Reviewed-on: https://go-review.googlesource.com/38732 Reviewed-by: Russ Cox <rsc@golang.org>
1 parent 846f925 commit 6e9ec14

File tree

3 files changed

+433
-60
lines changed

3 files changed

+433
-60
lines changed

Diff for: src/runtime/mfixalloc.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ type fixalloc struct {
2929
first func(arg, p unsafe.Pointer) // called first time p is returned
3030
arg unsafe.Pointer
3131
list *mlink
32-
chunk unsafe.Pointer
32+
chunk uintptr // use uintptr instead of unsafe.Pointer to avoid write barriers
3333
nchunk uint32
3434
inuse uintptr // in-use bytes now
3535
stat *uint64
@@ -54,7 +54,7 @@ func (f *fixalloc) init(size uintptr, first func(arg, p unsafe.Pointer), arg uns
5454
f.first = first
5555
f.arg = arg
5656
f.list = nil
57-
f.chunk = nil
57+
f.chunk = 0
5858
f.nchunk = 0
5959
f.inuse = 0
6060
f.stat = stat
@@ -77,15 +77,15 @@ func (f *fixalloc) alloc() unsafe.Pointer {
7777
return v
7878
}
7979
if uintptr(f.nchunk) < f.size {
80-
f.chunk = persistentalloc(_FixAllocChunk, 0, f.stat)
80+
f.chunk = uintptr(persistentalloc(_FixAllocChunk, 0, f.stat))
8181
f.nchunk = _FixAllocChunk
8282
}
8383

84-
v := f.chunk
84+
v := unsafe.Pointer(f.chunk)
8585
if f.first != nil {
8686
f.first(f.arg, v)
8787
}
88-
f.chunk = add(f.chunk, f.size)
88+
f.chunk = f.chunk + f.size
8989
f.nchunk -= uint32(f.size)
9090
f.inuse += f.size
9191
return v

Diff for: src/runtime/mgclarge.go

+327
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,327 @@
1+
// Copyright 2009 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// Page heap.
6+
//
7+
// See malloc.go for the general overview.
8+
//
9+
// Large spans are the subject of this file. Spans consisting of less than
10+
// _MaxMHeapLists are held in lists of like sized spans. Larger spans
11+
// are held in a treap. See https://en.wikipedia.org/wiki/Treap or
12+
// http://faculty.washington.edu/aragon/pubs/rst89.pdf for an overview.
13+
// sema.go also holds an implementation of a treap.
14+
//
15+
// Each treapNode holds a single span. The treap is sorted by page size
16+
// and for spans of the same size a secondary sort based on start address
17+
// is done.
18+
// Spans are returned based on a best fit algorithm and for spans of the same
19+
// size the one at the lowest address is selected.
20+
//
21+
// The primary routines are
22+
// insert: adds a span to the treap
23+
// remove: removes the span from that treap that best fits the required size
24+
// removeSpan: which removes a specific span from the treap
25+
//
26+
// _mheap.lock must be held when manipulating this data structure.
27+
//
28+
package runtime
29+
30+
import (
31+
"unsafe"
32+
)
33+
34+
//go:notinheap
35+
type mTreap struct {
36+
treap *treapNode
37+
}
38+
39+
//go:notinheap
40+
type treapNode struct {
41+
right *treapNode // all treapNodes > this treap node
42+
left *treapNode // all treapNodes < this treap node
43+
parent *treapNode // direct parent of this node, nil if root
44+
npagesKey uintptr // number of pages in spanKey, used as primary sort key
45+
spanKey *mspan // span of size npagesKey, used as secondary sort key
46+
priority uint32 // random number used by treap algorithm keep tree probablistically balanced
47+
}
48+
49+
func (t *treapNode) init() {
50+
t.right = nil
51+
t.left = nil
52+
t.parent = nil
53+
t.spanKey = nil
54+
t.npagesKey = 0
55+
t.priority = 0
56+
}
57+
58+
// isSpanInTreap is handy for debugging. One should hold the heap lock, usually
59+
// mheap_.lock().
60+
func (t *treapNode) isSpanInTreap(s *mspan) bool {
61+
if t == nil {
62+
return false
63+
}
64+
return t.spanKey == s || t.left.isSpanInTreap(s) || t.right.isSpanInTreap(s)
65+
}
66+
67+
// walkTreap is handy for debugging.
68+
// Starting at some treapnode t, for example the root, do a depth first preorder walk of
69+
// the tree executing fn at each treap node. One should hold the heap lock, usually
70+
// mheap_.lock().
71+
func (t *treapNode) walkTreap(fn func(tn *treapNode)) {
72+
if t == nil {
73+
return
74+
}
75+
fn(t)
76+
t.left.walkTreap(fn)
77+
t.right.walkTreap(fn)
78+
}
79+
80+
// checkTreapNode when used in conjunction with walkTreap can usually detect a
81+
// poorly formed treap.
82+
func checkTreapNode(t *treapNode) {
83+
// lessThan is used to order the treap.
84+
// npagesKey and npages are the primary keys.
85+
// spanKey and span are the secondary keys.
86+
// span == nil (0) will always be lessThan all
87+
// spans of the same size.
88+
lessThan := func(npages uintptr, s *mspan) bool {
89+
if t.npagesKey != npages {
90+
return t.npagesKey < npages
91+
}
92+
// t.npagesKey == npages
93+
return uintptr(unsafe.Pointer(t.spanKey)) < uintptr(unsafe.Pointer(s))
94+
}
95+
96+
if t == nil {
97+
return
98+
}
99+
if t.spanKey.npages != t.npagesKey || t.spanKey.next != nil {
100+
println("runtime: checkTreapNode treapNode t=", t, " t.npagesKey=", t.npagesKey,
101+
"t.spanKey.npages=", t.spanKey.npages)
102+
throw("why does span.npages and treap.ngagesKey do not match?")
103+
}
104+
if t.left != nil && lessThan(t.left.npagesKey, t.left.spanKey) {
105+
throw("t.lessThan(t.left.npagesKey, t.left.spanKey) is not false")
106+
}
107+
if t.right != nil && !lessThan(t.right.npagesKey, t.right.spanKey) {
108+
throw("!t.lessThan(t.left.npagesKey, t.left.spanKey) is not false")
109+
}
110+
}
111+
112+
// insert adds span to the large span treap.
113+
func (root *mTreap) insert(span *mspan) {
114+
npages := span.npages
115+
var last *treapNode
116+
pt := &root.treap
117+
for t := *pt; t != nil; t = *pt {
118+
last = t
119+
if t.npagesKey < npages {
120+
pt = &t.right
121+
} else if t.npagesKey > npages {
122+
pt = &t.left
123+
} else if uintptr(unsafe.Pointer(t.spanKey)) < uintptr(unsafe.Pointer(span)) {
124+
// t.npagesKey == npages, so sort on span addresses.
125+
pt = &t.right
126+
} else if uintptr(unsafe.Pointer(t.spanKey)) > uintptr(unsafe.Pointer(span)) {
127+
pt = &t.left
128+
} else {
129+
throw("inserting span already in treap")
130+
}
131+
}
132+
133+
// Add t as new leaf in tree of span size and unique addrs.
134+
// The balanced tree is a treap using priority as the random heap priority.
135+
// That is, it is a binary tree ordered according to the npagesKey,
136+
// but then among the space of possible binary trees respecting those
137+
// npagesKeys, it is kept balanced on average by maintaining a heap ordering
138+
// on the priority: s.priority <= both s.right.priority and s.right.priority.
139+
// https://en.wikipedia.org/wiki/Treap
140+
// http://faculty.washington.edu/aragon/pubs/rst89.pdf
141+
142+
t := (*treapNode)(mheap_.treapalloc.alloc())
143+
t.init()
144+
t.npagesKey = span.npages
145+
t.priority = fastrand()
146+
t.spanKey = span
147+
t.parent = last
148+
*pt = t // t now at a leaf.
149+
// Rotate up into tree according to priority.
150+
for t.parent != nil && t.parent.priority > t.priority {
151+
if t != nil && t.spanKey.npages != t.npagesKey {
152+
println("runtime: insert t=", t, "t.npagesKey=", t.npagesKey)
153+
println("runtime: t.spanKey=", t.spanKey, "t.spanKey.npages=", t.spanKey.npages)
154+
throw("span and treap sizes do not match?")
155+
}
156+
if t.parent.left == t {
157+
root.rotateRight(t.parent)
158+
} else {
159+
if t.parent.right != t {
160+
throw("treap insert finds a broken treap")
161+
}
162+
root.rotateLeft(t.parent)
163+
}
164+
}
165+
}
166+
167+
func (root *mTreap) removeNode(t *treapNode) *mspan {
168+
if t.spanKey.npages != t.npagesKey {
169+
throw("span and treap node npages do not match")
170+
}
171+
result := t.spanKey
172+
173+
// Rotate t down to be leaf of tree for removal, respecting priorities.
174+
for t.right != nil || t.left != nil {
175+
if t.right == nil || t.left != nil && t.left.priority < t.right.priority {
176+
root.rotateRight(t)
177+
} else {
178+
root.rotateLeft(t)
179+
}
180+
}
181+
// Remove t, now a leaf.
182+
if t.parent != nil {
183+
if t.parent.left == t {
184+
t.parent.left = nil
185+
} else {
186+
t.parent.right = nil
187+
}
188+
} else {
189+
root.treap = nil
190+
}
191+
// Return the found treapNode's span after freeing the treapNode.
192+
t.spanKey = nil
193+
t.npagesKey = 0
194+
mheap_.treapalloc.free(unsafe.Pointer(t))
195+
return result
196+
}
197+
198+
// remove searches for, finds, removes from the treap, and returns the smallest
199+
// span that can hold npages. If no span has at least npages return nil.
200+
// This is slightly more complicated than a simple binary tree search
201+
// since if an exact match is not found the next larger node is
202+
// returned.
203+
// If the last node inspected > npagesKey not holding
204+
// a left node (a smaller npages) is the "best fit" node.
205+
func (root *mTreap) remove(npages uintptr) *mspan {
206+
t := root.treap
207+
for t != nil {
208+
if t.spanKey == nil {
209+
throw("treap node with nil spanKey found")
210+
}
211+
if t.npagesKey < npages {
212+
t = t.right
213+
} else if t.left != nil && t.left.npagesKey >= npages {
214+
t = t.left
215+
} else {
216+
result := t.spanKey
217+
root.removeNode(t)
218+
return result
219+
}
220+
}
221+
return nil
222+
}
223+
224+
// removeSpan searches for, finds, deletes span along with
225+
// the associated treap node. If the span is not in the treap
226+
// then t will eventually be set to nil and the t.spanKey
227+
// will throw.
228+
func (root *mTreap) removeSpan(span *mspan) {
229+
npages := span.npages
230+
t := root.treap
231+
for t.spanKey != span {
232+
if t.npagesKey < npages {
233+
t = t.right
234+
} else if t.npagesKey > npages {
235+
t = t.left
236+
} else if uintptr(unsafe.Pointer(t.spanKey)) < uintptr(unsafe.Pointer(span)) {
237+
t = t.right
238+
} else if uintptr(unsafe.Pointer(t.spanKey)) > uintptr(unsafe.Pointer(span)) {
239+
t = t.left
240+
}
241+
}
242+
root.removeNode(t)
243+
return
244+
}
245+
246+
// scavengetreap visits each node in the treap and scavenges the
247+
// treapNode's span.
248+
func scavengetreap(treap *treapNode, now, limit uint64) uintptr {
249+
if treap == nil {
250+
return 0
251+
}
252+
return scavengeTreapNode(treap, now, limit) +
253+
scavengetreap(treap.left, now, limit) +
254+
scavengetreap(treap.right, now, limit)
255+
}
256+
257+
// rotateLeft rotates the tree rooted at node x.
258+
// turning (x a (y b c)) into (y (x a b) c).
259+
func (root *mTreap) rotateLeft(x *treapNode) {
260+
// p -> (x a (y b c))
261+
p := x.parent
262+
a, y := x.left, x.right
263+
b, c := y.left, y.right
264+
265+
y.left = x
266+
x.parent = y
267+
y.right = c
268+
if c != nil {
269+
c.parent = y
270+
}
271+
x.left = a
272+
if a != nil {
273+
a.parent = x
274+
}
275+
x.right = b
276+
if b != nil {
277+
b.parent = x
278+
}
279+
280+
y.parent = p
281+
if p == nil {
282+
root.treap = y
283+
} else if p.left == x {
284+
p.left = y
285+
} else {
286+
if p.right != x {
287+
throw("large span treap rotateLeft")
288+
}
289+
p.right = y
290+
}
291+
}
292+
293+
// rotateRight rotates the tree rooted at node y.
294+
// turning (y (x a b) c) into (x a (y b c)).
295+
func (root *mTreap) rotateRight(y *treapNode) {
296+
// p -> (y (x a b) c)
297+
p := y.parent
298+
x, c := y.left, y.right
299+
a, b := x.left, x.right
300+
301+
x.left = a
302+
if a != nil {
303+
a.parent = x
304+
}
305+
x.right = y
306+
y.parent = x
307+
y.left = b
308+
if b != nil {
309+
b.parent = y
310+
}
311+
y.right = c
312+
if c != nil {
313+
c.parent = y
314+
}
315+
316+
x.parent = p
317+
if p == nil {
318+
root.treap = x
319+
} else if p.left == y {
320+
p.left = x
321+
} else {
322+
if p.right != y {
323+
throw("large span treap rotateRight")
324+
}
325+
p.right = x
326+
}
327+
}

0 commit comments

Comments
 (0)