detached ec16513

purego: decrease number of allocs in RegisterFunc (#128)

This rewrites part of the RegisterFunc to have less code escape to the heap. Running the benchmark inside #121 shows a speed increase and alloc decrease.

goos: darwin
goarch: arm64
pkg: github.com/ebitengine/purego
BenchmarkCStringC
BenchmarkCStringC-10    	 2294838	       500.3 ns/op	     472 B/op	       8 allocs/op
PASS
commit
ec16513c8d00e4681bf3b00cca252619623ce3a6
author
TotallyGamerJet · · 2023-04-10 17:30 UTC
committer
GitHub · · 2023-04-10 17:30 UTC
parent
c3a82d7
signature
GPG Unverified4AEE18F83AFDEB23 The key is unknown
refs
tag: v0.4.0-alpha.3
2 files · +11 -4
M func.go +7 -4
@@ -127,7 +127,7 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
127127 }
128128 }
129129 var sysargs [maxArgs]uintptr
130- var stack = sysargs[numOfIntegerRegisters():]
130+ stack := sysargs[numOfIntegerRegisters():]
131131 var floats [numOfFloats]uintptr
132132 var numInts int
133133 var numFloats int
@@ -147,6 +147,7 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
147147 var keepAlive []interface{}
148148 defer func() {
149149 runtime.KeepAlive(keepAlive)
150+ runtime.KeepAlive(args)
150151 }()
151152 for _, v := range args {
152153 switch v.Kind() {
@@ -159,7 +160,7 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
159160 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
160161 addInt(uintptr(v.Int()))
161162 case reflect.Ptr, reflect.UnsafePointer, reflect.Slice:
162- keepAlive = append(keepAlive, v.Pointer())
163+ // There is no need to keepAlive this pointer separately because it is kept alive in the args variable
163164 addInt(v.Pointer())
164165 case reflect.Func:
165166 addInt(NewCallback(v.Interface()))
@@ -192,7 +193,8 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
192193 cfn,
193194 sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5], sysargs[6], sysargs[7], sysargs[8],
194195 floats[0], floats[1], floats[2], floats[3], floats[4], floats[5], floats[6], floats[7],
195- 0, 0, 0}
196+ 0, 0, 0,
197+ }
196198 runtime_cgocall(syscall9XABI0, unsafe.Pointer(&syscall))
197199 r1, r2 := syscall.r1, syscall.r2
198200
@@ -212,7 +214,8 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
212214 // We take the address and then dereference it to trick go vet from creating a possible miss-use of unsafe.Pointer
213215 v.SetPointer(*(*unsafe.Pointer)(unsafe.Pointer(&r1)))
214216 case reflect.Ptr:
215- v = reflect.NewAt(outType, unsafe.Pointer(&r1)).Elem()
217+ // It is safe to have the address of r1 not escape because it is immediately dereferenced with .Elem()
218+ v = reflect.NewAt(outType, runtime_noescape(unsafe.Pointer(&r1))).Elem()
216219 case reflect.Func:
217220 // wrap this C function in a nicely typed Go function
218221 v = reflect.New(outType)
@@ -127,7 +127,7 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
127 }127 }
128 }128 }
129 var sysargs [maxArgs]uintptr129 var sysargs [maxArgs]uintptr
130- var stack = sysargs[numOfIntegerRegisters():]130+ stack := sysargs[numOfIntegerRegisters():]
131 var floats [numOfFloats]uintptr131 var floats [numOfFloats]uintptr
132 var numInts int132 var numInts int
133 var numFloats int133 var numFloats int
@@ -147,6 +147,7 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
147 var keepAlive []interface{}147 var keepAlive []interface{}
148 defer func() {148 defer func() {
149 runtime.KeepAlive(keepAlive)149 runtime.KeepAlive(keepAlive)
150+ runtime.KeepAlive(args)
150 }()151 }()
151 for _, v := range args {152 for _, v := range args {
152 switch v.Kind() {153 switch v.Kind() {
@@ -159,7 +160,7 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
159 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:160 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
160 addInt(uintptr(v.Int()))161 addInt(uintptr(v.Int()))
161 case reflect.Ptr, reflect.UnsafePointer, reflect.Slice:162 case reflect.Ptr, reflect.UnsafePointer, reflect.Slice:
162- keepAlive = append(keepAlive, v.Pointer())163+ // There is no need to keepAlive this pointer separately because it is kept alive in the args variable
163 addInt(v.Pointer())164 addInt(v.Pointer())
164 case reflect.Func:165 case reflect.Func:
165 addInt(NewCallback(v.Interface()))166 addInt(NewCallback(v.Interface()))
@@ -192,7 +193,8 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
192 cfn,193 cfn,
193 sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5], sysargs[6], sysargs[7], sysargs[8],194 sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5], sysargs[6], sysargs[7], sysargs[8],
194 floats[0], floats[1], floats[2], floats[3], floats[4], floats[5], floats[6], floats[7],195 floats[0], floats[1], floats[2], floats[3], floats[4], floats[5], floats[6], floats[7],
195- 0, 0, 0}196+ 0, 0, 0,
197+ }
196 runtime_cgocall(syscall9XABI0, unsafe.Pointer(&syscall))198 runtime_cgocall(syscall9XABI0, unsafe.Pointer(&syscall))
197 r1, r2 := syscall.r1, syscall.r2199 r1, r2 := syscall.r1, syscall.r2
198 200
@@ -212,7 +214,8 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
212 // We take the address and then dereference it to trick go vet from creating a possible miss-use of unsafe.Pointer214 // We take the address and then dereference it to trick go vet from creating a possible miss-use of unsafe.Pointer
213 v.SetPointer(*(*unsafe.Pointer)(unsafe.Pointer(&r1)))215 v.SetPointer(*(*unsafe.Pointer)(unsafe.Pointer(&r1)))
214 case reflect.Ptr:216 case reflect.Ptr:
215- v = reflect.NewAt(outType, unsafe.Pointer(&r1)).Elem()217+ // It is safe to have the address of r1 not escape because it is immediately dereferenced with .Elem()
218+ v = reflect.NewAt(outType, runtime_noescape(unsafe.Pointer(&r1))).Elem()
216 case reflect.Func:219 case reflect.Func:
217 // wrap this C function in a nicely typed Go function220 // wrap this C function in a nicely typed Go function
218 v = reflect.New(outType)221 v = reflect.New(outType)
M go_runtime.go +4 -0
@@ -11,3 +11,7 @@ import (
1111
1212 //go:linkname runtime_cgocall runtime.cgocall
1313 func runtime_cgocall(fn uintptr, arg unsafe.Pointer) int32 // from runtime/sys_libc.go
14+
15+//go:linkname runtime_noescape runtime.noescape
16+//go:noescape
17+func runtime_noescape(p unsafe.Pointer) unsafe.Pointer // from runtime/stubs.go
@@ -11,3 +11,7 @@ import (
11 11
12 //go:linkname runtime_cgocall runtime.cgocall12 //go:linkname runtime_cgocall runtime.cgocall
13 func runtime_cgocall(fn uintptr, arg unsafe.Pointer) int32 // from runtime/sys_libc.go13 func runtime_cgocall(fn uintptr, arg unsafe.Pointer) int32 // from runtime/sys_libc.go
14+
15+//go:linkname runtime_noescape runtime.noescape
16+//go:noescape
17+func runtime_noescape(p unsafe.Pointer) unsafe.Pointer // from runtime/stubs.go

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