Skip to content

Commit ec47ad5

Browse files
committed
runtime: Simplify slice growing/appending code
Refactor the slice appending function to rely on the slice growing function, and remove branches/loops to use a branchfree variant. Signed-off-by: L. Pereira <[email protected]>
1 parent 725518f commit ec47ad5

File tree

1 file changed

+18
-42
lines changed

1 file changed

+18
-42
lines changed

src/runtime/slice.go

Lines changed: 18 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,42 +3,26 @@ package runtime
33
// This file implements compiler builtins for slices: append() and copy().
44

55
import (
6+
"math/bits"
67
"unsafe"
78
)
89

910
// Builtin append(src, elements...) function: append elements to src and return
1011
// 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-
}
16-
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)
31-
32-
// Copy the old slice to the new slice.
33-
if srcLen != 0 {
34-
memmove(buf, srcBuf, srcLen*elemSize)
12+
func sliceAppend(srcBuf, elemsBuf unsafe.Pointer, srcLen, srcCap, elemsLen, elemSize uintptr) (unsafe.Pointer, uintptr, uintptr) {
13+
newLen := srcLen + elemsLen
14+
if newLen > srcLen {
15+
// Allocate a new slice with capacity for elemsLen more elements, if necessary;
16+
// otherwise, reuse the passed slice.
17+
srcBuf, _, srcCap = sliceGrow(srcBuf, srcLen, srcCap, newLen, elemSize)
18+
19+
if elemsLen > 0 {
20+
// Append the new elements in-place.
21+
memmove(unsafe.Add(srcBuf, srcLen*elemSize), elemsBuf, elemsLen*elemSize)
3522
}
36-
srcBuf = buf
3723
}
3824

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
25+
return srcBuf, newLen, srcCap
4226
}
4327

4428
// Builtin copy(dst, src) function: copy bytes from dst to src.
@@ -54,29 +38,21 @@ func sliceCopy(dst, src unsafe.Pointer, dstLen, srcLen uintptr, elemSize uintptr
5438

5539
// sliceGrow returns a new slice with space for at least newCap elements
5640
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-
6041
if oldCap >= newCap {
6142
// No need to grow, return the input slice.
6243
return oldBuf, oldLen, oldCap
6344
}
6445

65-
// allow nil slice
66-
if oldCap == 0 {
67-
oldCap++
68-
}
69-
70-
// grow capacity
71-
for oldCap < newCap {
72-
oldCap *= 2
73-
}
46+
// This can be made more memory-efficient by multiplying by some other constant, such as 1.5,
47+
// which seems to be allowed by the Go language specification (but this can be observed by
48+
// programs).
49+
newCap = 1 << (32 - bits.LeadingZeros32(uint32(newCap)))
7450

75-
buf := alloc(oldCap*elemSize, nil)
51+
buf := alloc(newCap*elemSize, nil)
7652
if oldLen > 0 {
7753
// copy any data to new slice
7854
memmove(buf, oldBuf, oldLen*elemSize)
7955
}
8056

81-
return buf, oldLen, oldCap
57+
return buf, oldLen, newCap
8258
}

0 commit comments

Comments
 (0)