@@ -8,37 +8,19 @@ import (
8
8
9
9
// Builtin append(src, elements...) function: append elements to src and return
10
10
// the modified (possibly expanded) slice.
11
- func sliceAppend (srcBuf , elemsBuf unsafe.Pointer , srcLen , srcCap , elemsLen uintptr , elemSize uintptr ) (unsafe.Pointer , uintptr , uintptr ) {
12
- if elemsLen == 0 {
13
- // Nothing to append, return the input slice.
14
- return srcBuf , srcLen , srcCap
15
- }
11
+ func sliceAppend (srcBuf , elemsBuf unsafe.Pointer , srcLen , srcCap , elemsLen , elemSize uintptr ) (unsafe.Pointer , uintptr , uintptr ) {
12
+ newLen := srcLen + elemsLen
16
13
17
- if srcLen + elemsLen > srcCap {
18
- // Slice does not fit, allocate a new buffer that's large enough.
19
- srcCap = srcCap * 2
20
- if srcCap == 0 { // e.g. zero slice
21
- srcCap = 1
22
- }
23
- for srcLen + elemsLen > srcCap {
24
- // This algorithm may be made more memory-efficient: don't multiply
25
- // by two but by 1.5 or something. As far as I can see, that's
26
- // allowed by the Go language specification (but may be observed by
27
- // programs).
28
- srcCap *= 2
29
- }
30
- buf := alloc (srcCap * elemSize , nil )
14
+ if elemsLen != 0 {
15
+ // Allocate a new slice with capacity for elemsLen more elements, if necessary;
16
+ // otherwise, reuse the passed slice.
17
+ srcBuf , srcLen , srcCap = sliceGrow (srcBuf , srcLen , srcCap , newLen , elemSize )
31
18
32
- // Copy the old slice to the new slice.
33
- if srcLen != 0 {
34
- memmove (buf , srcBuf , srcLen * elemSize )
35
- }
36
- srcBuf = buf
19
+ // Append the new elements in-place.
20
+ memmove (unsafe .Add (srcBuf , srcLen * elemSize ), elemsBuf , elemsLen * elemSize )
37
21
}
38
22
39
- // The slice fits (after possibly allocating a new one), append it in-place.
40
- memmove (unsafe .Add (srcBuf , srcLen * elemSize ), elemsBuf , elemsLen * elemSize )
41
- return srcBuf , srcLen + elemsLen , srcCap
23
+ return srcBuf , newLen , srcCap
42
24
}
43
25
44
26
// Builtin copy(dst, src) function: copy bytes from dst to src.
@@ -54,29 +36,28 @@ func sliceCopy(dst, src unsafe.Pointer, dstLen, srcLen uintptr, elemSize uintptr
54
36
55
37
// sliceGrow returns a new slice with space for at least newCap elements
56
38
func sliceGrow (oldBuf unsafe.Pointer , oldLen , oldCap , newCap , elemSize uintptr ) (unsafe.Pointer , uintptr , uintptr ) {
57
-
58
- // TODO(dgryski): sliceGrow() and sliceAppend() should be refactored to share the base growth code.
59
-
60
39
if oldCap >= newCap {
61
40
// No need to grow, return the input slice.
62
41
return oldBuf , oldLen , oldCap
63
42
}
64
43
65
- // allow nil slice
66
- if oldCap == 0 {
67
- oldCap ++
68
- }
44
+ // Mildly over-allocate using a similar integer sequence to what CPython uses when
45
+ // allocating lists. This still helps amortizing repeated appends to this slice,
46
+ // but is much more conservative than doubling the capacity every time a reallocation
47
+ // is necessary.
48
+ // Sequence goes as follows: 0, 8, 16, 24, 32, 40, 48, 56, 64, 74, 80, ...
49
+ newCap = (newCap + (newCap >> 3 ) + 6 ) & ^ uintptr (7 )
69
50
70
- // grow capacity
71
- for oldCap < newCap {
72
- oldCap *= 2
51
+ // Account for nil slices.
52
+ if newCap == 0 {
53
+ newCap ++
73
54
}
74
55
75
- buf := alloc (oldCap * elemSize , nil )
56
+ buf := alloc (newCap * elemSize , nil )
76
57
if oldLen > 0 {
77
58
// copy any data to new slice
78
59
memmove (buf , oldBuf , oldLen * elemSize )
79
60
}
80
61
81
- return buf , oldLen , oldCap
62
+ return buf , oldLen , newCap
82
63
}
0 commit comments