detached 2fe737a

purego: add benchmarks for calling methods (#363)

Add benchmarks comparing SyscallN, RegisterFunc, and callback performance
across different argument counts. This helps measure and compare the overhead
of different calling approaches in purego's function invocation system.

Closes #362
commit
2fe737a44c710ca5665742a00102cf612fa88f24
author
Travis Cline · · 2026-02-07 19:31 UTC
committer
GitHub · · 2026-02-07 19:31 UTC
parent
49bede1
signature
GPG UnverifiedB5690EEEBB952194 The key is unknown
2 files · +356 -0
A syscall_bench_test.go +275 -0
@@ -0,0 +1,275 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2026 The Ebitengine Authors
3+
4+package purego_test
5+
6+import (
7+ "fmt"
8+ "os"
9+ "path/filepath"
10+ "testing"
11+
12+ "github.com/ebitengine/purego"
13+ "github.com/ebitengine/purego/internal/load"
14+)
15+
16+// BenchmarkCallingMethods compares RegisterFunc, SyscallN, and Callback methods
17+func BenchmarkCallingMethods(b *testing.B) {
18+ testCases := []struct {
19+ n int
20+ goFn any
21+ goFnPtr uintptr
22+ cFnPtr uintptr
23+ cFnName string
24+ cCallbackPtr uintptr
25+ cCallbackName string
26+ args []int64
27+ expectedSum int64
28+ }{
29+ {1, goSum1, 0, 0, "sum1_c", 0, "call_callback1", []int64{1}, 1},
30+ {2, goSum2, 0, 0, "sum2_c", 0, "call_callback2", []int64{1, 2}, 3},
31+ {3, goSum3, 0, 0, "sum3_c", 0, "call_callback3", []int64{1, 2, 3}, 6},
32+ {5, goSum5, 0, 0, "sum5_c", 0, "call_callback5", []int64{1, 2, 3, 4, 5}, 15},
33+ {10, goSum10, 0, 0, "sum10_c", 0, "call_callback10", []int64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 55},
34+ {14, goSum15, 0, 0, "sum14_c", 0, "call_callback14", []int64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14}, 105},
35+ {15, goSum15, 0, 0, "sum15_c", 0, "call_callback15", []int64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, 120},
36+ }
37+
38+ // Build C library for benchmarking
39+ libFileName := filepath.Join(b.TempDir(), "libbenchmark.so")
40+ if err := buildSharedLib("CC", libFileName, filepath.Join("testdata", "benchmarktest", "benchmark.c")); err != nil {
41+ b.Fatalf("Failed to build C library: %v", err)
42+ }
43+ b.Cleanup(func() {
44+ os.Remove(libFileName)
45+ })
46+
47+ libHandle, err := load.OpenLibrary(libFileName)
48+ if err != nil {
49+ b.Fatalf("Failed to load C library: %v", err)
50+ }
51+ b.Cleanup(func() {
52+ if err := load.CloseLibrary(libHandle); err != nil {
53+ b.Fatalf("Failed to close library: %s", err)
54+ }
55+ })
56+
57+ // Create callbacks and load C functions
58+ for i := range testCases {
59+ testCases[i].goFnPtr = purego.NewCallback(testCases[i].goFn)
60+
61+ cFn, err := load.OpenSymbol(libHandle, testCases[i].cFnName)
62+ if err != nil {
63+ b.Fatalf("Failed to load C function %s: %v", testCases[i].cFnName, err)
64+ }
65+ testCases[i].cFnPtr = cFn
66+
67+ cCallbackFn, err := load.OpenSymbol(libHandle, testCases[i].cCallbackName)
68+ if err != nil {
69+ b.Fatalf("Failed to load C callback wrapper %s: %v", testCases[i].cCallbackName, err)
70+ }
71+ testCases[i].cCallbackPtr = cCallbackFn
72+ }
73+
74+ b.Run("RegisterFunc/Callback", func(b *testing.B) {
75+ for _, tc := range testCases {
76+ b.Run(fmt.Sprintf("%dargs", tc.n), func(b *testing.B) {
77+ b.ReportAllocs()
78+ registerFn := makeRegisterFunc(tc.n)
79+ purego.RegisterFunc(registerFn, tc.goFnPtr)
80+
81+ b.ResetTimer()
82+ result := callRegisterFunc(registerFn, tc.n, tc.args, b.N)
83+ b.StopTimer()
84+
85+ if result != tc.expectedSum {
86+ b.Fatalf("RegisterFunc/Callback: expected sum %d, got %d", tc.expectedSum, result)
87+ }
88+ })
89+ }
90+ })
91+
92+ // Benchmark RegisterFunc with C functions
93+ b.Run("RegisterFunc/CFunc", func(b *testing.B) {
94+ for _, tc := range testCases {
95+ b.Run(fmt.Sprintf("%dargs", tc.n), func(b *testing.B) {
96+ b.ReportAllocs()
97+ registerFn := makeRegisterFunc(tc.n)
98+ purego.RegisterFunc(registerFn, tc.cFnPtr)
99+
100+ b.ResetTimer()
101+ result := callRegisterFunc(registerFn, tc.n, tc.args, b.N)
102+ b.StopTimer()
103+
104+ if result != tc.expectedSum {
105+ b.Fatalf("RegisterFunc/CFunc: expected sum %d, got %d", tc.expectedSum, result)
106+ }
107+ })
108+ }
109+ })
110+
111+ // Benchmark SyscallN with Go callbacks
112+ b.Run("SyscallN/Callback", func(b *testing.B) {
113+ for _, tc := range testCases {
114+ b.Run(fmt.Sprintf("%dargs", tc.n), func(b *testing.B) {
115+ b.ReportAllocs()
116+ args := int64sToUintptrs(tc.args)
117+ var result uintptr
118+ b.ResetTimer()
119+ for i := 0; i < b.N; i++ {
120+ result, _, _ = purego.SyscallN(tc.goFnPtr, args...)
121+ }
122+ b.StopTimer()
123+ if int64(result) != tc.expectedSum {
124+ b.Fatalf("SyscallN/Callback: expected sum %d, got %d", tc.expectedSum, result)
125+ }
126+ })
127+ }
128+ })
129+
130+ // Benchmark SyscallN with C functions
131+ b.Run("SyscallN/CFunc", func(b *testing.B) {
132+ for _, tc := range testCases {
133+ b.Run(fmt.Sprintf("%dargs", tc.n), func(b *testing.B) {
134+ b.ReportAllocs()
135+ args := int64sToUintptrs(tc.args)
136+ var result uintptr
137+ b.ResetTimer()
138+ for i := 0; i < b.N; i++ {
139+ result, _, _ = purego.SyscallN(tc.cFnPtr, args...)
140+ }
141+ b.StopTimer()
142+ if int64(result) != tc.expectedSum {
143+ b.Fatalf("SyscallN/CFunc: expected sum %d, got %d", tc.expectedSum, result)
144+ }
145+ })
146+ }
147+ })
148+
149+ // Benchmark round-trip: Go → C → Go callback (realistic use case)
150+ b.Run("RoundTrip/GoC", func(b *testing.B) {
151+ for _, tc := range testCases {
152+ b.Run(fmt.Sprintf("%dargs", tc.n), func(b *testing.B) {
153+ b.ReportAllocs()
154+ // Build args: first arg is callback pointer, rest are the arguments
155+ args := int64sToUintptrs(tc.args)
156+ callbackArgs := make([]uintptr, len(args)+1)
157+ callbackArgs[0] = tc.goFnPtr
158+ copy(callbackArgs[1:], args)
159+
160+ // Skip if total args (callback + args) exceeds or meets limit
161+ // SyscallN has issues with exactly 15 or more arguments
162+ if len(callbackArgs) >= 15 {
163+ b.Skipf("Round-trip with %d args + callback (%d total) exceeds/meets SyscallN limit", tc.n, len(callbackArgs))
164+ }
165+
166+ var result uintptr
167+ b.ResetTimer()
168+ for i := 0; i < b.N; i++ {
169+ result, _, _ = purego.SyscallN(tc.cCallbackPtr, callbackArgs...)
170+ }
171+ b.StopTimer()
172+ if int64(result) != tc.expectedSum {
173+ b.Fatalf("RoundTrip: expected sum %d, got %d", tc.expectedSum, result)
174+ }
175+ })
176+ }
177+ })
178+}
179+
180+// makeRegisterFunc creates a function pointer of the appropriate signature
181+func makeRegisterFunc(n int) any {
182+ switch n {
183+ case 1:
184+ return new(func(int64) int64)
185+ case 2:
186+ return new(func(int64, int64) int64)
187+ case 3:
188+ return new(func(int64, int64, int64) int64)
189+ case 5:
190+ return new(func(int64, int64, int64, int64, int64) int64)
191+ case 10:
192+ return new(func(int64, int64, int64, int64, int64, int64, int64, int64, int64, int64) int64)
193+ case 14:
194+ return new(func(int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64) int64)
195+ case 15:
196+ return new(func(int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64) int64)
197+ default:
198+ panic(fmt.Sprintf("unsupported arg count: %d", n))
199+ }
200+}
201+
202+// callRegisterFunc calls the registered function with the appropriate number of arguments
203+func callRegisterFunc(registerFn any, n int, args []int64, iterations int) int64 {
204+ var result int64
205+ switch n {
206+ case 1:
207+ f := registerFn.(*func(int64) int64)
208+ for i := 0; i < iterations; i++ {
209+ result = (*f)(args[0])
210+ }
211+ case 2:
212+ f := registerFn.(*func(int64, int64) int64)
213+ for i := 0; i < iterations; i++ {
214+ result = (*f)(args[0], args[1])
215+ }
216+ case 3:
217+ f := registerFn.(*func(int64, int64, int64) int64)
218+ for i := 0; i < iterations; i++ {
219+ result = (*f)(args[0], args[1], args[2])
220+ }
221+ case 5:
222+ f := registerFn.(*func(int64, int64, int64, int64, int64) int64)
223+ for i := 0; i < iterations; i++ {
224+ result = (*f)(args[0], args[1], args[2], args[3], args[4])
225+ }
226+ case 10:
227+ f := registerFn.(*func(int64, int64, int64, int64, int64, int64, int64, int64, int64, int64) int64)
228+ for i := 0; i < iterations; i++ {
229+ result = (*f)(args[0], args[1], args[2], args[3], args[4],
230+ args[5], args[6], args[7], args[8], args[9])
231+ }
232+ case 14:
233+ f := registerFn.(*func(int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64) int64)
234+ for i := 0; i < iterations; i++ {
235+ result = (*f)(args[0], args[1], args[2], args[3], args[4],
236+ args[5], args[6], args[7], args[8], args[9],
237+ args[10], args[11], args[12], args[13])
238+ }
239+ case 15:
240+ f := registerFn.(*func(int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64) int64)
241+ for i := 0; i < iterations; i++ {
242+ result = (*f)(args[0], args[1], args[2], args[3], args[4],
243+ args[5], args[6], args[7], args[8], args[9],
244+ args[10], args[11], args[12], args[13], args[14])
245+ }
246+ default:
247+ panic(fmt.Sprintf("unsupported arg count: %d", n))
248+ }
249+ return result
250+}
251+
252+// int64sToUintptrs converts []int64 to []uintptr for SyscallN
253+func int64sToUintptrs(args []int64) []uintptr {
254+ result := make([]uintptr, len(args))
255+ for i, v := range args {
256+ result[i] = uintptr(v)
257+ }
258+ return result
259+}
260+
261+func goSum1(a1 int64) int64 { return a1 }
262+
263+func goSum2(a1, a2 int64) int64 { return a1 + a2 }
264+
265+func goSum3(a1, a2, a3 int64) int64 { return a1 + a2 + a3 }
266+
267+func goSum5(a1, a2, a3, a4, a5 int64) int64 { return a1 + a2 + a3 + a4 + a5 }
268+
269+func goSum10(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10 int64) int64 {
270+ return a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9 + a10
271+}
272+
273+func goSum15(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 int64) int64 {
274+ return a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9 + a10 + a11 + a12 + a13 + a14 + a15
275+}
@@ -0,0 +1,275 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2026 The Ebitengine Authors
3+
4+package purego_test
5+
6+import (
7+ "fmt"
8+ "os"
9+ "path/filepath"
10+ "testing"
11+
12+ "github.com/ebitengine/purego"
13+ "github.com/ebitengine/purego/internal/load"
14+)
15+
16+// BenchmarkCallingMethods compares RegisterFunc, SyscallN, and Callback methods
17+func BenchmarkCallingMethods(b *testing.B) {
18+ testCases := []struct {
19+ n int
20+ goFn any
21+ goFnPtr uintptr
22+ cFnPtr uintptr
23+ cFnName string
24+ cCallbackPtr uintptr
25+ cCallbackName string
26+ args []int64
27+ expectedSum int64
28+ }{
29+ {1, goSum1, 0, 0, "sum1_c", 0, "call_callback1", []int64{1}, 1},
30+ {2, goSum2, 0, 0, "sum2_c", 0, "call_callback2", []int64{1, 2}, 3},
31+ {3, goSum3, 0, 0, "sum3_c", 0, "call_callback3", []int64{1, 2, 3}, 6},
32+ {5, goSum5, 0, 0, "sum5_c", 0, "call_callback5", []int64{1, 2, 3, 4, 5}, 15},
33+ {10, goSum10, 0, 0, "sum10_c", 0, "call_callback10", []int64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 55},
34+ {14, goSum15, 0, 0, "sum14_c", 0, "call_callback14", []int64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14}, 105},
35+ {15, goSum15, 0, 0, "sum15_c", 0, "call_callback15", []int64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, 120},
36+ }
37+
38+ // Build C library for benchmarking
39+ libFileName := filepath.Join(b.TempDir(), "libbenchmark.so")
40+ if err := buildSharedLib("CC", libFileName, filepath.Join("testdata", "benchmarktest", "benchmark.c")); err != nil {
41+ b.Fatalf("Failed to build C library: %v", err)
42+ }
43+ b.Cleanup(func() {
44+ os.Remove(libFileName)
45+ })
46+
47+ libHandle, err := load.OpenLibrary(libFileName)
48+ if err != nil {
49+ b.Fatalf("Failed to load C library: %v", err)
50+ }
51+ b.Cleanup(func() {
52+ if err := load.CloseLibrary(libHandle); err != nil {
53+ b.Fatalf("Failed to close library: %s", err)
54+ }
55+ })
56+
57+ // Create callbacks and load C functions
58+ for i := range testCases {
59+ testCases[i].goFnPtr = purego.NewCallback(testCases[i].goFn)
60+
61+ cFn, err := load.OpenSymbol(libHandle, testCases[i].cFnName)
62+ if err != nil {
63+ b.Fatalf("Failed to load C function %s: %v", testCases[i].cFnName, err)
64+ }
65+ testCases[i].cFnPtr = cFn
66+
67+ cCallbackFn, err := load.OpenSymbol(libHandle, testCases[i].cCallbackName)
68+ if err != nil {
69+ b.Fatalf("Failed to load C callback wrapper %s: %v", testCases[i].cCallbackName, err)
70+ }
71+ testCases[i].cCallbackPtr = cCallbackFn
72+ }
73+
74+ b.Run("RegisterFunc/Callback", func(b *testing.B) {
75+ for _, tc := range testCases {
76+ b.Run(fmt.Sprintf("%dargs", tc.n), func(b *testing.B) {
77+ b.ReportAllocs()
78+ registerFn := makeRegisterFunc(tc.n)
79+ purego.RegisterFunc(registerFn, tc.goFnPtr)
80+
81+ b.ResetTimer()
82+ result := callRegisterFunc(registerFn, tc.n, tc.args, b.N)
83+ b.StopTimer()
84+
85+ if result != tc.expectedSum {
86+ b.Fatalf("RegisterFunc/Callback: expected sum %d, got %d", tc.expectedSum, result)
87+ }
88+ })
89+ }
90+ })
91+
92+ // Benchmark RegisterFunc with C functions
93+ b.Run("RegisterFunc/CFunc", func(b *testing.B) {
94+ for _, tc := range testCases {
95+ b.Run(fmt.Sprintf("%dargs", tc.n), func(b *testing.B) {
96+ b.ReportAllocs()
97+ registerFn := makeRegisterFunc(tc.n)
98+ purego.RegisterFunc(registerFn, tc.cFnPtr)
99+
100+ b.ResetTimer()
101+ result := callRegisterFunc(registerFn, tc.n, tc.args, b.N)
102+ b.StopTimer()
103+
104+ if result != tc.expectedSum {
105+ b.Fatalf("RegisterFunc/CFunc: expected sum %d, got %d", tc.expectedSum, result)
106+ }
107+ })
108+ }
109+ })
110+
111+ // Benchmark SyscallN with Go callbacks
112+ b.Run("SyscallN/Callback", func(b *testing.B) {
113+ for _, tc := range testCases {
114+ b.Run(fmt.Sprintf("%dargs", tc.n), func(b *testing.B) {
115+ b.ReportAllocs()
116+ args := int64sToUintptrs(tc.args)
117+ var result uintptr
118+ b.ResetTimer()
119+ for i := 0; i < b.N; i++ {
120+ result, _, _ = purego.SyscallN(tc.goFnPtr, args...)
121+ }
122+ b.StopTimer()
123+ if int64(result) != tc.expectedSum {
124+ b.Fatalf("SyscallN/Callback: expected sum %d, got %d", tc.expectedSum, result)
125+ }
126+ })
127+ }
128+ })
129+
130+ // Benchmark SyscallN with C functions
131+ b.Run("SyscallN/CFunc", func(b *testing.B) {
132+ for _, tc := range testCases {
133+ b.Run(fmt.Sprintf("%dargs", tc.n), func(b *testing.B) {
134+ b.ReportAllocs()
135+ args := int64sToUintptrs(tc.args)
136+ var result uintptr
137+ b.ResetTimer()
138+ for i := 0; i < b.N; i++ {
139+ result, _, _ = purego.SyscallN(tc.cFnPtr, args...)
140+ }
141+ b.StopTimer()
142+ if int64(result) != tc.expectedSum {
143+ b.Fatalf("SyscallN/CFunc: expected sum %d, got %d", tc.expectedSum, result)
144+ }
145+ })
146+ }
147+ })
148+
149+ // Benchmark round-trip: Go → C → Go callback (realistic use case)
150+ b.Run("RoundTrip/GoC", func(b *testing.B) {
151+ for _, tc := range testCases {
152+ b.Run(fmt.Sprintf("%dargs", tc.n), func(b *testing.B) {
153+ b.ReportAllocs()
154+ // Build args: first arg is callback pointer, rest are the arguments
155+ args := int64sToUintptrs(tc.args)
156+ callbackArgs := make([]uintptr, len(args)+1)
157+ callbackArgs[0] = tc.goFnPtr
158+ copy(callbackArgs[1:], args)
159+
160+ // Skip if total args (callback + args) exceeds or meets limit
161+ // SyscallN has issues with exactly 15 or more arguments
162+ if len(callbackArgs) >= 15 {
163+ b.Skipf("Round-trip with %d args + callback (%d total) exceeds/meets SyscallN limit", tc.n, len(callbackArgs))
164+ }
165+
166+ var result uintptr
167+ b.ResetTimer()
168+ for i := 0; i < b.N; i++ {
169+ result, _, _ = purego.SyscallN(tc.cCallbackPtr, callbackArgs...)
170+ }
171+ b.StopTimer()
172+ if int64(result) != tc.expectedSum {
173+ b.Fatalf("RoundTrip: expected sum %d, got %d", tc.expectedSum, result)
174+ }
175+ })
176+ }
177+ })
178+}
179+
180+// makeRegisterFunc creates a function pointer of the appropriate signature
181+func makeRegisterFunc(n int) any {
182+ switch n {
183+ case 1:
184+ return new(func(int64) int64)
185+ case 2:
186+ return new(func(int64, int64) int64)
187+ case 3:
188+ return new(func(int64, int64, int64) int64)
189+ case 5:
190+ return new(func(int64, int64, int64, int64, int64) int64)
191+ case 10:
192+ return new(func(int64, int64, int64, int64, int64, int64, int64, int64, int64, int64) int64)
193+ case 14:
194+ return new(func(int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64) int64)
195+ case 15:
196+ return new(func(int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64) int64)
197+ default:
198+ panic(fmt.Sprintf("unsupported arg count: %d", n))
199+ }
200+}
201+
202+// callRegisterFunc calls the registered function with the appropriate number of arguments
203+func callRegisterFunc(registerFn any, n int, args []int64, iterations int) int64 {
204+ var result int64
205+ switch n {
206+ case 1:
207+ f := registerFn.(*func(int64) int64)
208+ for i := 0; i < iterations; i++ {
209+ result = (*f)(args[0])
210+ }
211+ case 2:
212+ f := registerFn.(*func(int64, int64) int64)
213+ for i := 0; i < iterations; i++ {
214+ result = (*f)(args[0], args[1])
215+ }
216+ case 3:
217+ f := registerFn.(*func(int64, int64, int64) int64)
218+ for i := 0; i < iterations; i++ {
219+ result = (*f)(args[0], args[1], args[2])
220+ }
221+ case 5:
222+ f := registerFn.(*func(int64, int64, int64, int64, int64) int64)
223+ for i := 0; i < iterations; i++ {
224+ result = (*f)(args[0], args[1], args[2], args[3], args[4])
225+ }
226+ case 10:
227+ f := registerFn.(*func(int64, int64, int64, int64, int64, int64, int64, int64, int64, int64) int64)
228+ for i := 0; i < iterations; i++ {
229+ result = (*f)(args[0], args[1], args[2], args[3], args[4],
230+ args[5], args[6], args[7], args[8], args[9])
231+ }
232+ case 14:
233+ f := registerFn.(*func(int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64) int64)
234+ for i := 0; i < iterations; i++ {
235+ result = (*f)(args[0], args[1], args[2], args[3], args[4],
236+ args[5], args[6], args[7], args[8], args[9],
237+ args[10], args[11], args[12], args[13])
238+ }
239+ case 15:
240+ f := registerFn.(*func(int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64) int64)
241+ for i := 0; i < iterations; i++ {
242+ result = (*f)(args[0], args[1], args[2], args[3], args[4],
243+ args[5], args[6], args[7], args[8], args[9],
244+ args[10], args[11], args[12], args[13], args[14])
245+ }
246+ default:
247+ panic(fmt.Sprintf("unsupported arg count: %d", n))
248+ }
249+ return result
250+}
251+
252+// int64sToUintptrs converts []int64 to []uintptr for SyscallN
253+func int64sToUintptrs(args []int64) []uintptr {
254+ result := make([]uintptr, len(args))
255+ for i, v := range args {
256+ result[i] = uintptr(v)
257+ }
258+ return result
259+}
260+
261+func goSum1(a1 int64) int64 { return a1 }
262+
263+func goSum2(a1, a2 int64) int64 { return a1 + a2 }
264+
265+func goSum3(a1, a2, a3 int64) int64 { return a1 + a2 + a3 }
266+
267+func goSum5(a1, a2, a3, a4, a5 int64) int64 { return a1 + a2 + a3 + a4 + a5 }
268+
269+func goSum10(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10 int64) int64 {
270+ return a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9 + a10
271+}
272+
273+func goSum15(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 int64) int64 {
274+ return a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9 + a10 + a11 + a12 + a13 + a14 + a15
275+}
A testdata/benchmarktest/benchmark.c +81 -0
@@ -0,0 +1,81 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2026 The Ebitengine Authors
3+
4+#include <stdint.h>
5+
6+int64_t sum1_c(int64_t a1) { return a1; }
7+
8+int64_t sum2_c(int64_t a1, int64_t a2) { return a1 + a2; }
9+
10+int64_t sum3_c(int64_t a1, int64_t a2, int64_t a3) { return a1 + a2 + a3; }
11+
12+int64_t sum5_c(int64_t a1, int64_t a2, int64_t a3, int64_t a4, int64_t a5) {
13+ return a1 + a2 + a3 + a4 + a5;
14+}
15+
16+int64_t sum10_c(int64_t a1, int64_t a2, int64_t a3, int64_t a4, int64_t a5,
17+ int64_t a6, int64_t a7, int64_t a8, int64_t a9, int64_t a10) {
18+ return a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9 + a10;
19+}
20+
21+int64_t sum14_c(int64_t a1, int64_t a2, int64_t a3, int64_t a4, int64_t a5,
22+ int64_t a6, int64_t a7, int64_t a8, int64_t a9, int64_t a10,
23+ int64_t a11, int64_t a12, int64_t a13, int64_t a14) {
24+ return a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9 + a10 + a11 + a12 + a13 +
25+ a14;
26+}
27+
28+int64_t sum15_c(int64_t a1, int64_t a2, int64_t a3, int64_t a4, int64_t a5,
29+ int64_t a6, int64_t a7, int64_t a8, int64_t a9, int64_t a10,
30+ int64_t a11, int64_t a12, int64_t a13, int64_t a14,
31+ int64_t a15) {
32+ return a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9 + a10 + a11 + a12 + a13 +
33+ a14 + a15;
34+}
35+
36+typedef int64_t (*callback1_t)(int64_t);
37+int64_t call_callback1(callback1_t cb, int64_t a1) { return cb(a1); }
38+
39+typedef int64_t (*callback2_t)(int64_t, int64_t);
40+int64_t call_callback2(callback2_t cb, int64_t a1, int64_t a2) {
41+ return cb(a1, a2);
42+}
43+
44+typedef int64_t (*callback3_t)(int64_t, int64_t, int64_t);
45+int64_t call_callback3(callback3_t cb, int64_t a1, int64_t a2, int64_t a3) {
46+ return cb(a1, a2, a3);
47+}
48+
49+typedef int64_t (*callback5_t)(int64_t, int64_t, int64_t, int64_t, int64_t);
50+int64_t call_callback5(callback5_t cb, int64_t a1, int64_t a2, int64_t a3,
51+ int64_t a4, int64_t a5) {
52+ return cb(a1, a2, a3, a4, a5);
53+}
54+
55+typedef int64_t (*callback10_t)(int64_t, int64_t, int64_t, int64_t, int64_t,
56+ int64_t, int64_t, int64_t, int64_t, int64_t);
57+int64_t call_callback10(callback10_t cb, int64_t a1, int64_t a2, int64_t a3,
58+ int64_t a4, int64_t a5, int64_t a6, int64_t a7,
59+ int64_t a8, int64_t a9, int64_t a10) {
60+ return cb(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10);
61+}
62+
63+typedef int64_t (*callback14_t)(int64_t, int64_t, int64_t, int64_t, int64_t,
64+ int64_t, int64_t, int64_t, int64_t, int64_t,
65+ int64_t, int64_t, int64_t, int64_t);
66+int64_t call_callback14(callback14_t cb, int64_t a1, int64_t a2, int64_t a3,
67+ int64_t a4, int64_t a5, int64_t a6, int64_t a7,
68+ int64_t a8, int64_t a9, int64_t a10, int64_t a11,
69+ int64_t a12, int64_t a13, int64_t a14) {
70+ return cb(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14);
71+}
72+
73+typedef int64_t (*callback15_t)(int64_t, int64_t, int64_t, int64_t, int64_t,
74+ int64_t, int64_t, int64_t, int64_t, int64_t,
75+ int64_t, int64_t, int64_t, int64_t, int64_t);
76+int64_t call_callback15(callback15_t cb, int64_t a1, int64_t a2, int64_t a3,
77+ int64_t a4, int64_t a5, int64_t a6, int64_t a7,
78+ int64_t a8, int64_t a9, int64_t a10, int64_t a11,
79+ int64_t a12, int64_t a13, int64_t a14, int64_t a15) {
80+ return cb(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15);
81+}
@@ -0,0 +1,81 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2026 The Ebitengine Authors
3+
4+#include <stdint.h>
5+
6+int64_t sum1_c(int64_t a1) { return a1; }
7+
8+int64_t sum2_c(int64_t a1, int64_t a2) { return a1 + a2; }
9+
10+int64_t sum3_c(int64_t a1, int64_t a2, int64_t a3) { return a1 + a2 + a3; }
11+
12+int64_t sum5_c(int64_t a1, int64_t a2, int64_t a3, int64_t a4, int64_t a5) {
13+ return a1 + a2 + a3 + a4 + a5;
14+}
15+
16+int64_t sum10_c(int64_t a1, int64_t a2, int64_t a3, int64_t a4, int64_t a5,
17+ int64_t a6, int64_t a7, int64_t a8, int64_t a9, int64_t a10) {
18+ return a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9 + a10;
19+}
20+
21+int64_t sum14_c(int64_t a1, int64_t a2, int64_t a3, int64_t a4, int64_t a5,
22+ int64_t a6, int64_t a7, int64_t a8, int64_t a9, int64_t a10,
23+ int64_t a11, int64_t a12, int64_t a13, int64_t a14) {
24+ return a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9 + a10 + a11 + a12 + a13 +
25+ a14;
26+}
27+
28+int64_t sum15_c(int64_t a1, int64_t a2, int64_t a3, int64_t a4, int64_t a5,
29+ int64_t a6, int64_t a7, int64_t a8, int64_t a9, int64_t a10,
30+ int64_t a11, int64_t a12, int64_t a13, int64_t a14,
31+ int64_t a15) {
32+ return a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9 + a10 + a11 + a12 + a13 +
33+ a14 + a15;
34+}
35+
36+typedef int64_t (*callback1_t)(int64_t);
37+int64_t call_callback1(callback1_t cb, int64_t a1) { return cb(a1); }
38+
39+typedef int64_t (*callback2_t)(int64_t, int64_t);
40+int64_t call_callback2(callback2_t cb, int64_t a1, int64_t a2) {
41+ return cb(a1, a2);
42+}
43+
44+typedef int64_t (*callback3_t)(int64_t, int64_t, int64_t);
45+int64_t call_callback3(callback3_t cb, int64_t a1, int64_t a2, int64_t a3) {
46+ return cb(a1, a2, a3);
47+}
48+
49+typedef int64_t (*callback5_t)(int64_t, int64_t, int64_t, int64_t, int64_t);
50+int64_t call_callback5(callback5_t cb, int64_t a1, int64_t a2, int64_t a3,
51+ int64_t a4, int64_t a5) {
52+ return cb(a1, a2, a3, a4, a5);
53+}
54+
55+typedef int64_t (*callback10_t)(int64_t, int64_t, int64_t, int64_t, int64_t,
56+ int64_t, int64_t, int64_t, int64_t, int64_t);
57+int64_t call_callback10(callback10_t cb, int64_t a1, int64_t a2, int64_t a3,
58+ int64_t a4, int64_t a5, int64_t a6, int64_t a7,
59+ int64_t a8, int64_t a9, int64_t a10) {
60+ return cb(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10);
61+}
62+
63+typedef int64_t (*callback14_t)(int64_t, int64_t, int64_t, int64_t, int64_t,
64+ int64_t, int64_t, int64_t, int64_t, int64_t,
65+ int64_t, int64_t, int64_t, int64_t);
66+int64_t call_callback14(callback14_t cb, int64_t a1, int64_t a2, int64_t a3,
67+ int64_t a4, int64_t a5, int64_t a6, int64_t a7,
68+ int64_t a8, int64_t a9, int64_t a10, int64_t a11,
69+ int64_t a12, int64_t a13, int64_t a14) {
70+ return cb(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14);
71+}
72+
73+typedef int64_t (*callback15_t)(int64_t, int64_t, int64_t, int64_t, int64_t,
74+ int64_t, int64_t, int64_t, int64_t, int64_t,
75+ int64_t, int64_t, int64_t, int64_t, int64_t);
76+int64_t call_callback15(callback15_t cb, int64_t a1, int64_t a2, int64_t a3,
77+ int64_t a4, int64_t a5, int64_t a6, int64_t a7,
78+ int64_t a8, int64_t a9, int64_t a10, int64_t a11,
79+ int64_t a12, int64_t a13, int64_t a14, int64_t a15) {
80+ return cb(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15);
81+}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 102

↑↓ moveenter openesc close