-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathbatched.go
112 lines (91 loc) · 3.05 KB
/
batched.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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
package main
/*
#cgo CFLAGS: -fno-inline-functions
void foo() {
}
void emptyStack(int size, void* data) {
int i;
for(i = 0; i < size; i++) {
// Grab function pointer.
void* fptr = data+i;
// Grab number of arguments.
long nargs = (long)data+i+1;
// Skip over the arguments (in real practice we would unpack the memory
// when making the call).
i += nargs;
foo();
}
}
*/
import "C"
import (
"flag"
"log"
"time"
"unsafe"
)
var (
// The number of functions that can exist in a batch before it must be
// executed in C-land.
//
// In actual practice there is no limit because any function that returns
// something (either directly or via a return-value pointer argument) means
// that the pending batch MUST be executed now as the callee expects the
// return value to be made immedietly available.
//
// I chose 25 here because that is, in my experience, the average number of
// OpenGL calls you can make before invoking a function that returns
// something.
BatchSize = flag.Int("bs", 25, "number of function calls per batch")
// The number of arguments to (fakely) push onto the stack to measure the
// overhead of pushing arguments onto the stack.
//
// Five was chosen as that is a good average for OpenGL functions.
NArgs = flag.Int("args", 5, "number of fake arguments (to test stack overhead)")
// 350k C calls is what we will measure the performance of. This is what I
// would expect would be needed for an AAA game.
//
// The higher the number of calls, the more CGO overhead there is.
NCalls = flag.Int("calls", 350000, "number of C function calls to perform")
)
func main() {
log.SetFlags(0)
flag.Parse()
log.Println("Benchmarking:")
log.Println(" Batch Size:", *BatchSize)
log.Println(" Number Of Calls:", *NCalls)
log.Println(" Number Of Args:", *NArgs)
log.Println("")
// Measure the time it takes to simply perform CGO calls directly.
start := time.Now()
for i := 0; i < *NCalls; i++ {
C.foo()
}
cgoTime := time.Since(start)
log.Println("CGO", cgoTime)
// Measure the time it takes to perform CGO calls through batching.
// Initialize the stack. In practice we just let the stack grow to any size
// and we re-use the space (by slicing to zero).
stack := make([]unsafe.Pointer, 0, (*BatchSize)*(*NArgs))
// Reset the timer.
start = time.Now()
for i := 0; i < *NCalls; i++ {
// Push fake function pointer onto the stack (would be used by a jump
// switch in actual practice).
stack = append(stack, unsafe.Pointer(uintptr(0)))
// Push fake arguments onto the stack
for a := 0; a < *NArgs; a++ {
stack = append(stack, unsafe.Pointer(uintptr(i)))
}
// At every batchSize, we execute the pending batch using C.emptyStack.
if (i % *BatchSize) == 0 {
C.emptyStack(C.int(len(stack)), unsafe.Pointer(&stack[0]))
// Slice the stack back to zero (we reuse the space later).
stack = stack[:0]
}
}
batchingTime := time.Since(start)
log.Println("Batching", batchingTime)
// Print GitHub syntax for data:
log.Printf("%d | %d | %d | %v | %v", *BatchSize, *NCalls, *NArgs, cgoTime, batchingTime)
}