Skip to content

Commit a705b73

Browse files
authored
Merge pull request #946 from devlights/add-sliceclone-benchmark
Add benchmarks/03.slice_clone
2 parents c3fe1b4 + 68e2b26 commit a705b73

File tree

2 files changed

+184
-0
lines changed

2 files changed

+184
-0
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# https://taskfile.dev
2+
3+
version: '3'
4+
5+
tasks:
6+
default:
7+
cmds:
8+
- go test -bench . -benchmem -count 3 | tee bench.txt
9+
- defer: rm -f bench.txt
10+
- benchstat bench.txt
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
package main
2+
3+
import (
4+
"slices"
5+
"testing"
6+
)
7+
8+
const (
9+
NumItems = 5 * 1024 * 1024
10+
)
11+
12+
// コンパイラの最適化を防ぐためのダミー関数
13+
func voidfn(_ []int) {}
14+
15+
// 方法1: 事前に容量を確保してからappendする方法
16+
func BenchmarkPreAllocatedAppend(b *testing.B) {
17+
var (
18+
items = make([]int, 0, NumItems)
19+
)
20+
// テストデータの準備
21+
for i := range NumItems {
22+
items = append(items, i)
23+
}
24+
25+
for b.Loop() {
26+
var (
27+
dst = make([]int, 0, NumItems)
28+
)
29+
// appendを使用してコピー
30+
dst = append(dst, items...)
31+
voidfn(dst) // コンパイラの最適化を防ぐ
32+
}
33+
}
34+
35+
// 方法2: 空スライスに直接appendする方法
36+
func BenchmarkEmptySliceAppend(b *testing.B) {
37+
var (
38+
items = make([]int, 0, NumItems)
39+
)
40+
for i := range NumItems {
41+
items = append(items, i)
42+
}
43+
44+
for b.Loop() {
45+
// 空スライスに直接append
46+
dst := append([]int{}, items...)
47+
voidfn(dst)
48+
}
49+
}
50+
51+
// 方法3: 容量をリセットしてからappendする方法
52+
func BenchmarkCapacityResetAppend(b *testing.B) {
53+
var (
54+
items = make([]int, 0, NumItems)
55+
)
56+
for i := range NumItems {
57+
items = append(items, i)
58+
}
59+
60+
for b.Loop() {
61+
var (
62+
dst = make([]int, 0, NumItems)
63+
)
64+
// スライスの長さと容量を0にリセットしてからappend
65+
dst = append(dst[:0:0], items...)
66+
voidfn(dst)
67+
}
68+
}
69+
70+
// 方法4: 従来のcopy()を使った方法
71+
func BenchmarkBuiltinCopy(b *testing.B) {
72+
var (
73+
items = make([]int, 0, NumItems)
74+
)
75+
for i := range NumItems {
76+
items = append(items, i)
77+
}
78+
79+
for b.Loop() {
80+
// 正しい長さでスライスを作成
81+
dst := make([]int, len(items))
82+
// copyを使用してコピー(最も効率的)
83+
copy(dst, items)
84+
voidfn(dst)
85+
}
86+
}
87+
88+
// 方法5: slices.Clone()を使った方法
89+
func BenchmarkSlicesClone(b *testing.B) {
90+
var (
91+
items = make([]int, 0, NumItems)
92+
)
93+
for i := range NumItems {
94+
items = append(items, i)
95+
}
96+
97+
for b.Loop() {
98+
// slices.Cloneを使用(内部でmake + copyを実行)
99+
dst := slices.Clone(items)
100+
voidfn(dst)
101+
}
102+
}
103+
104+
/*
105+
$ task
106+
task: [default] go test -bench . -benchmem -count 3 | tee bench.txt
107+
goos: linux
108+
goarch: amd64
109+
pkg: github.com/devlights/try-golang/examples/benchmarks/03.slice_clone
110+
cpu: Intel(R) Core(TM) Ultra 5 125H
111+
BenchmarkPreAllocatedAppend-18 134 8116946 ns/op 41943090 B/op 1 allocs/op
112+
BenchmarkPreAllocatedAppend-18 150 7897353 ns/op 41943049 B/op 1 allocs/op
113+
BenchmarkPreAllocatedAppend-18 153 7697424 ns/op 41943048 B/op 1 allocs/op
114+
BenchmarkEmptySliceAppend-18 234 5234496 ns/op 41943072 B/op 1 allocs/op
115+
BenchmarkEmptySliceAppend-18 246 4907417 ns/op 41943081 B/op 1 allocs/op
116+
BenchmarkEmptySliceAppend-18 174 6013573 ns/op 41943054 B/op 1 allocs/op
117+
BenchmarkCapacityResetAppend-18 153 7743873 ns/op 83886086 B/op 2 allocs/op
118+
BenchmarkCapacityResetAppend-18 152 7804002 ns/op 83886092 B/op 2 allocs/op
119+
BenchmarkCapacityResetAppend-18 150 7891077 ns/op 83886088 B/op 2 allocs/op
120+
BenchmarkBuiltinCopy-18 151 7796522 ns/op 41943046 B/op 1 allocs/op
121+
BenchmarkBuiltinCopy-18 152 7813188 ns/op 41943050 B/op 1 allocs/op
122+
BenchmarkBuiltinCopy-18 158 7525531 ns/op 41943050 B/op 1 allocs/op
123+
BenchmarkSlicesClone-18 255 5556701 ns/op 41943047 B/op 1 allocs/op
124+
BenchmarkSlicesClone-18 187 6222304 ns/op 41943047 B/op 1 allocs/op
125+
BenchmarkSlicesClone-18 204 5850473 ns/op 41943044 B/op 1 allocs/op
126+
PASS
127+
ok github.com/devlights/try-golang/examples/benchmarks/03.slice_clone 17.955s
128+
task: [default] benchstat bench.txt
129+
goos: linux
130+
goarch: amd64
131+
pkg: github.com/devlights/try-golang/examples/benchmarks/03.slice_clone
132+
cpu: Intel(R) Core(TM) Ultra 5 125H
133+
│ bench.txt │
134+
│ sec/op │
135+
PreAllocatedAppend-18 7.897m ± ∞ ¹
136+
EmptySliceAppend-18 5.234m ± ∞ ¹
137+
CapacityResetAppend-18 7.804m ± ∞ ¹
138+
BuiltinCopy-18 7.797m ± ∞ ¹
139+
SlicesClone-18 5.850m ± ∞ ¹
140+
geomean 6.816m
141+
¹ need >= 6 samples for confidence interval at level 0.95
142+
143+
│ bench.txt │
144+
│ B/op │
145+
PreAllocatedAppend-18 40.00Mi ± ∞ ¹
146+
EmptySliceAppend-18 40.00Mi ± ∞ ¹
147+
CapacityResetAppend-18 80.00Mi ± ∞ ¹
148+
BuiltinCopy-18 40.00Mi ± ∞ ¹
149+
SlicesClone-18 40.00Mi ± ∞ ¹
150+
geomean 45.95Mi
151+
¹ need >= 6 samples for confidence interval at level 0.95
152+
153+
│ bench.txt │
154+
│ allocs/op │
155+
PreAllocatedAppend-18 1.000 ± ∞ ¹
156+
EmptySliceAppend-18 1.000 ± ∞ ¹
157+
CapacityResetAppend-18 2.000 ± ∞ ¹
158+
BuiltinCopy-18 1.000 ± ∞ ¹
159+
SlicesClone-18 1.000 ± ∞ ¹
160+
geomean 1.149
161+
¹ need >= 6 samples for confidence interval at level 0.95
162+
task: [default] rm -f bench.txt
163+
*/
164+
165+
/*
166+
結果として、何度試しても空スライスにAppendする方法が最も速かった。
167+
つまり、
168+
dst := append([]int{}, items...)
169+
が速い。
170+
slices.Clone()は、内部で上記を行っている
171+
// Avoid s[:0:0] as it leads to unwanted liveness when cloning a
172+
// zero-length slice of a large array; see https://go.dev/issue/68488.
173+
return append(S{}, s...)
174+
*/

0 commit comments

Comments
 (0)