-
Notifications
You must be signed in to change notification settings - Fork 17
/
Copy pathparameter_test.go
92 lines (81 loc) · 2.65 KB
/
parameter_test.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
package benchmark
import (
"math/rand"
"testing"
"unsafe"
)
type Big struct {
// Making the struct bigger will
A [128]byte
}
func (b Big) byValue(x int) byte {
r := byte(x)
// Check every 32th element. This is faster
// than checking each element but still triggers
// reads on all cache lines as 32 is smaller than
// the cache line size of modern CPUs.
// We want this loop to be as fast as possible so the
// percentual difference between our test cases is
// as big as possible.
for i := 0; i < len(b.A); i += 32 {
r += b.A[i]
}
return r
}
func (b *Big) byPointer(x int) byte {
r := byte(x)
for i := 0; i < len(b.A); i += 32 {
r += b.A[i]
}
return r
}
var (
as []Big
)
func setup() {
if as == nil {
// Allocate 1GB of Big structs.
as = make([]Big, 1024*1024*1024/unsafe.Sizeof(Big{}))
// Make sure the memory isn't just zeroed pages and
// is all mapped to our address space.
// If we don't do this the first test case would be slower
// as it is triggering all the page faults.
for i := 0; i < len(as); i++ {
for j := 0; j < len(as[i].A); j += 32 {
as[i].A[j] = byte(rand.Intn(256))
}
}
}
rand.Seed(0)
}
func BenchmarkParameterPassedByPointer(b *testing.B) {
setup()
b.ResetTimer()
// We randomly pick an element out of a 1GB big slice.
// This way we assume the memory isn't already in the CPU cache.
for i := 0; i < b.N; i++ {
// Reading the value the pointer points to from memory will be slow on the first
// byte but after that the memory will be in the CPU cache and following reads will be fast.
as[rand.Intn(len(as))].byPointer(i)
}
}
func BenchmarkParameterPassedByValue(b *testing.B) {
setup()
b.ResetTimer()
for i := 0; i < b.N; i++ {
// Passing the parameter by value means Go will make a copy on the stack.
// Reading the original value from memory will be slow but writing
// the copy to the stack will be fast as it will be put into the CPU cache.
// After this the function will read form the copy in the CPU cache which is fast again.
// So while passing by pointer means extra instructions to dereference the pointer,
// passing by value means extra writes to the CPU cache and extra CPU cache usage.
// Since the CPU cache is very fast the difference between passing by pointer and
// passing by value only gets significant with really big structs.
//
// In most cases passing by value should be preferred as it makes it easier for the Go escape
// analysis to allocate the struct on the stack instead of the heap.
// If passing by pointer makes your struct be allocated on the heap you'll get much
// worse performance that passing by value.
as[rand.Intn(len(as))].byValue(i)
}
}