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 Unverified
4AEE18F83AFDEB23The 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) { | |||
| 127 | 127 | } | |
| 128 | 128 | } | |
| 129 | 129 | var sysargs [maxArgs]uintptr | |
| 130 | - | var stack = sysargs[numOfIntegerRegisters():] | |
| 130 | + | stack := sysargs[numOfIntegerRegisters():] | |
| 131 | 131 | var floats [numOfFloats]uintptr | |
| 132 | 132 | var numInts int | |
| 133 | 133 | var numFloats int | |
| @@ -147,6 +147,7 @@ func RegisterFunc(fptr interface{}, cfn uintptr) { | |||
| 147 | 147 | var keepAlive []interface{} | |
| 148 | 148 | defer func() { | |
| 149 | 149 | runtime.KeepAlive(keepAlive) | |
| 150 | + | runtime.KeepAlive(args) | |
| 150 | 151 | }() | |
| 151 | 152 | for _, v := range args { | |
| 152 | 153 | switch v.Kind() { | |
| @@ -159,7 +160,7 @@ func RegisterFunc(fptr interface{}, cfn uintptr) { | |||
| 159 | 160 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: | |
| 160 | 161 | addInt(uintptr(v.Int())) | |
| 161 | 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 | 164 | addInt(v.Pointer()) | |
| 164 | 165 | case reflect.Func: | |
| 165 | 166 | addInt(NewCallback(v.Interface())) | |
| @@ -192,7 +193,8 @@ func RegisterFunc(fptr interface{}, cfn uintptr) { | |||
| 192 | 193 | cfn, | |
| 193 | 194 | sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5], sysargs[6], sysargs[7], sysargs[8], | |
| 194 | 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 | 198 | runtime_cgocall(syscall9XABI0, unsafe.Pointer(&syscall)) | |
| 197 | 199 | r1, r2 := syscall.r1, syscall.r2 | |
| 198 | 200 | ||
| @@ -212,7 +214,8 @@ func RegisterFunc(fptr interface{}, cfn uintptr) { | |||
| 212 | 214 | // We take the address and then dereference it to trick go vet from creating a possible miss-use of unsafe.Pointer | |
| 213 | 215 | v.SetPointer(*(*unsafe.Pointer)(unsafe.Pointer(&r1))) | |
| 214 | 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 | 219 | case reflect.Func: | |
| 217 | 220 | // wrap this C function in a nicely typed Go function | |
| 218 | 221 | v = reflect.New(outType) | |
| @@ -127,7 +127,7 @@ func RegisterFunc(fptr interface{}, cfn uintptr) { | |||||
| 127 | } | 127 | } | ||
| 128 | } | 128 | } | ||
| 129 | var sysargs [maxArgs]uintptr | 129 | var sysargs [maxArgs]uintptr | ||
| 130 | - | var stack = sysargs[numOfIntegerRegisters():] | 130 | + | stack := sysargs[numOfIntegerRegisters():] |
| 131 | var floats [numOfFloats]uintptr | 131 | var floats [numOfFloats]uintptr | ||
| 132 | var numInts int | 132 | var numInts int | ||
| 133 | var numFloats int | 133 | 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.r2 | 199 | 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.Pointer | 214 | // 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 function | 220 | // 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 ( | |||
| 11 | 11 | ||
| 12 | 12 | //go:linkname runtime_cgocall runtime.cgocall | |
| 13 | 13 | 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.cgocall | 12 | //go:linkname runtime_cgocall runtime.cgocall | ||
| 13 | func runtime_cgocall(fn uintptr, arg unsafe.Pointer) int32 // from runtime/sys_libc.go | 13 | 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 | |||