detached 0196f2d

all: improve memory usage (#282)

Don't allocate when being called by objc.Send. This is done by just inlining the expansion of the parameters directly into the argument list instead of making a tmp slice.

Use a sync.Pool for syscall15Args struct to save on allocations
commit
0196f2d3cc8275725fe6476b32409caa244b1a8a
author
TotallyGamerJet · · 2024-10-18 02:31 UTC
committer
GitHub · · 2024-10-18 02:31 UTC
parent
7402fed
signature
GPG UnverifiedB5690EEEBB952194 The key is unknown
1 file · +59 -45
M func.go +59 -45
@@ -10,11 +10,16 @@ import (
1010 "math"
1111 "reflect"
1212 "runtime"
13+ "sync"
1314 "unsafe"
1415
1516 "github.com/ebitengine/purego/internal/strings"
1617 )
1718
19+var thePool = sync.Pool{New: func() any {
20+ return new(syscall15Args)
21+}}
22+
1823 // RegisterLibFunc is a wrapper around RegisterFunc that uses the C function returned from Dlsym(handle, name).
1924 // It panics if it can't find the name symbol.
2025 func RegisterLibFunc(fptr interface{}, handle uintptr, name string) {
@@ -200,18 +205,6 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
200205 }
201206 }
202207 v := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) {
203- if len(args) > 0 {
204- if variadic, ok := args[len(args)-1].Interface().([]interface{}); ok {
205- // subtract one from args bc the last argument in args is []interface{}
206- // which we are currently expanding
207- tmp := make([]reflect.Value, len(args)-1+len(variadic))
208- n := copy(tmp, args[:len(args)-1])
209- for i, v := range variadic {
210- tmp[n+i] = reflect.ValueOf(v)
211- }
212- args = tmp
213- }
214- }
215208 var sysargs [maxArgs]uintptr
216209 stack := sysargs[numOfIntegerRegisters():]
217210 var floats [numOfFloats]uintptr
@@ -260,7 +253,8 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
260253 runtime.KeepAlive(keepAlive)
261254 runtime.KeepAlive(args)
262255 }()
263- var syscall syscall15Args
256+ syscall := thePool.Get().(*syscall15Args)
257+ defer thePool.Put(syscall)
264258 if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct {
265259 outType := ty.Out(0)
266260 if runtime.GOARCH == "amd64" && outType.Size() > maxRegAllocStructSize {
@@ -276,40 +270,21 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
276270 }
277271 }
278272 }
279- for _, v := range args {
280- switch v.Kind() {
281- case reflect.String:
282- ptr := strings.CString(v.String())
283- keepAlive = append(keepAlive, ptr)
284- addInt(uintptr(unsafe.Pointer(ptr)))
285- case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
286- addInt(uintptr(v.Uint()))
287- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
288- addInt(uintptr(v.Int()))
289- case reflect.Ptr, reflect.UnsafePointer, reflect.Slice:
290- // There is no need to keepAlive this pointer separately because it is kept alive in the args variable
291- addInt(v.Pointer())
292- case reflect.Func:
293- addInt(NewCallback(v.Interface()))
294- case reflect.Bool:
295- if v.Bool() {
296- addInt(1)
297- } else {
298- addInt(0)
273+ for i, v := range args {
274+ if variadic, ok := args[i].Interface().([]interface{}); ok {
275+ if i != len(args)-1 {
276+ panic("purego: can only expand last parameter")
299277 }
300- case reflect.Float32:
301- addFloat(uintptr(math.Float32bits(float32(v.Float()))))
302- case reflect.Float64:
303- addFloat(uintptr(math.Float64bits(v.Float())))
304- case reflect.Struct:
305- keepAlive = addStruct(v, &numInts, &numFloats, &numStack, addInt, addFloat, addStack, keepAlive)
306- default:
307- panic("purego: unsupported kind: " + v.Kind().String())
278+ for _, x := range variadic {
279+ keepAlive = addValue(reflect.ValueOf(x), keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)
280+ }
281+ continue
308282 }
283+ keepAlive = addValue(v, keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)
309284 }
310285 if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" {
311286 // Use the normal arm64 calling convention even on Windows
312- syscall = syscall15Args{
287+ *syscall = syscall15Args{
313288 cfn,
314289 sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5],
315290 sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11],
@@ -317,7 +292,7 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
317292 floats[0], floats[1], floats[2], floats[3], floats[4], floats[5], floats[6], floats[7],
318293 syscall.arm64_r8,
319294 }
320- runtime_cgocall(syscall15XABI0, unsafe.Pointer(&syscall))
295+ runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall))
321296 } else {
322297 // This is a fallback for Windows amd64, 386, and arm. Note this may not support floats
323298 syscall.a1, syscall.a2, _ = syscall_syscall15X(cfn, sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4],
@@ -357,15 +332,54 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
357332 // On 32bit platforms syscall.r2 is the upper part of a 64bit return.
358333 v.SetFloat(math.Float64frombits(uint64(syscall.f1)))
359334 case reflect.Struct:
360- v = getStruct(outType, syscall)
335+ v = getStruct(outType, *syscall)
361336 default:
362337 panic("purego: unsupported return kind: " + outType.Kind().String())
363338 }
364- return []reflect.Value{v}
339+ if len(args) > 0 {
340+ // reuse args slice instead of allocating one when possible
341+ args[0] = v
342+ return args[:1]
343+ } else {
344+ return []reflect.Value{v}
345+ }
365346 })
366347 fn.Set(v)
367348 }
368349
350+func addValue(v reflect.Value, keepAlive []interface{}, addInt func(x uintptr), addFloat func(x uintptr), addStack func(x uintptr), numInts *int, numFloats *int, numStack *int) []interface{} {
351+ switch v.Kind() {
352+ case reflect.String:
353+ ptr := strings.CString(v.String())
354+ keepAlive = append(keepAlive, ptr)
355+ addInt(uintptr(unsafe.Pointer(ptr)))
356+ case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
357+ addInt(uintptr(v.Uint()))
358+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
359+ addInt(uintptr(v.Int()))
360+ case reflect.Ptr, reflect.UnsafePointer, reflect.Slice:
361+ // There is no need to keepAlive this pointer separately because it is kept alive in the args variable
362+ addInt(v.Pointer())
363+ case reflect.Func:
364+ addInt(NewCallback(v.Interface()))
365+ case reflect.Bool:
366+ if v.Bool() {
367+ addInt(1)
368+ } else {
369+ addInt(0)
370+ }
371+ case reflect.Float32:
372+ addFloat(uintptr(math.Float32bits(float32(v.Float()))))
373+ case reflect.Float64:
374+ addFloat(uintptr(math.Float64bits(v.Float())))
375+ case reflect.Struct:
376+ keepAlive = addStruct(v, numInts, numFloats, numStack, addInt, addFloat, addStack, keepAlive)
377+ default:
378+ panic("purego: unsupported kind: " + v.Kind().String())
379+ }
380+ return keepAlive
381+}
382+
369383 // maxRegAllocStructSize is the biggest a struct can be while still fitting in registers.
370384 // if it is bigger than this than enough space must be allocated on the heap and then passed into
371385 // the function as the first parameter on amd64 or in R8 on arm64.
@@ -10,11 +10,16 @@ import (
10 "math"10 "math"
11 "reflect"11 "reflect"
12 "runtime"12 "runtime"
13+ "sync"
13 "unsafe"14 "unsafe"
14 15
15 "github.com/ebitengine/purego/internal/strings"16 "github.com/ebitengine/purego/internal/strings"
16 )17 )
17 18
19+var thePool = sync.Pool{New: func() any {
20+ return new(syscall15Args)
21+}}
22+
18 // RegisterLibFunc is a wrapper around RegisterFunc that uses the C function returned from Dlsym(handle, name).23 // RegisterLibFunc is a wrapper around RegisterFunc that uses the C function returned from Dlsym(handle, name).
19 // It panics if it can't find the name symbol.24 // It panics if it can't find the name symbol.
20 func RegisterLibFunc(fptr interface{}, handle uintptr, name string) {25 func RegisterLibFunc(fptr interface{}, handle uintptr, name string) {
@@ -200,18 +205,6 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
200 }205 }
201 }206 }
202 v := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) {207 v := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) {
203- if len(args) > 0 {
204- if variadic, ok := args[len(args)-1].Interface().([]interface{}); ok {
205- // subtract one from args bc the last argument in args is []interface{}
206- // which we are currently expanding
207- tmp := make([]reflect.Value, len(args)-1+len(variadic))
208- n := copy(tmp, args[:len(args)-1])
209- for i, v := range variadic {
210- tmp[n+i] = reflect.ValueOf(v)
211- }
212- args = tmp
213- }
214- }
215 var sysargs [maxArgs]uintptr208 var sysargs [maxArgs]uintptr
216 stack := sysargs[numOfIntegerRegisters():]209 stack := sysargs[numOfIntegerRegisters():]
217 var floats [numOfFloats]uintptr210 var floats [numOfFloats]uintptr
@@ -260,7 +253,8 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
260 runtime.KeepAlive(keepAlive)253 runtime.KeepAlive(keepAlive)
261 runtime.KeepAlive(args)254 runtime.KeepAlive(args)
262 }()255 }()
263- var syscall syscall15Args256+ syscall := thePool.Get().(*syscall15Args)
257+ defer thePool.Put(syscall)
264 if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct {258 if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct {
265 outType := ty.Out(0)259 outType := ty.Out(0)
266 if runtime.GOARCH == "amd64" && outType.Size() > maxRegAllocStructSize {260 if runtime.GOARCH == "amd64" && outType.Size() > maxRegAllocStructSize {
@@ -276,40 +270,21 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
276 }270 }
277 }271 }
278 }272 }
279- for _, v := range args {273+ for i, v := range args {
280- switch v.Kind() {274+ if variadic, ok := args[i].Interface().([]interface{}); ok {
281- case reflect.String:275+ if i != len(args)-1 {
282- ptr := strings.CString(v.String())276+ panic("purego: can only expand last parameter")
283- keepAlive = append(keepAlive, ptr)
284- addInt(uintptr(unsafe.Pointer(ptr)))
285- case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
286- addInt(uintptr(v.Uint()))
287- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
288- addInt(uintptr(v.Int()))
289- case reflect.Ptr, reflect.UnsafePointer, reflect.Slice:
290- // There is no need to keepAlive this pointer separately because it is kept alive in the args variable
291- addInt(v.Pointer())
292- case reflect.Func:
293- addInt(NewCallback(v.Interface()))
294- case reflect.Bool:
295- if v.Bool() {
296- addInt(1)
297- } else {
298- addInt(0)
299 }277 }
300- case reflect.Float32:278+ for _, x := range variadic {
301- addFloat(uintptr(math.Float32bits(float32(v.Float()))))279+ keepAlive = addValue(reflect.ValueOf(x), keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)
302- case reflect.Float64:280+ }
303- addFloat(uintptr(math.Float64bits(v.Float())))281+ continue
304- case reflect.Struct:
305- keepAlive = addStruct(v, &numInts, &numFloats, &numStack, addInt, addFloat, addStack, keepAlive)
306- default:
307- panic("purego: unsupported kind: " + v.Kind().String())
308 }282 }
283+ keepAlive = addValue(v, keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)
309 }284 }
310 if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" {285 if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" {
311 // Use the normal arm64 calling convention even on Windows286 // Use the normal arm64 calling convention even on Windows
312- syscall = syscall15Args{287+ *syscall = syscall15Args{
313 cfn,288 cfn,
314 sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5],289 sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5],
315 sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11],290 sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11],
@@ -317,7 +292,7 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
317 floats[0], floats[1], floats[2], floats[3], floats[4], floats[5], floats[6], floats[7],292 floats[0], floats[1], floats[2], floats[3], floats[4], floats[5], floats[6], floats[7],
318 syscall.arm64_r8,293 syscall.arm64_r8,
319 }294 }
320- runtime_cgocall(syscall15XABI0, unsafe.Pointer(&syscall))295+ runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall))
321 } else {296 } else {
322 // This is a fallback for Windows amd64, 386, and arm. Note this may not support floats297 // This is a fallback for Windows amd64, 386, and arm. Note this may not support floats
323 syscall.a1, syscall.a2, _ = syscall_syscall15X(cfn, sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4],298 syscall.a1, syscall.a2, _ = syscall_syscall15X(cfn, sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4],
@@ -357,15 +332,54 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
357 // On 32bit platforms syscall.r2 is the upper part of a 64bit return.332 // On 32bit platforms syscall.r2 is the upper part of a 64bit return.
358 v.SetFloat(math.Float64frombits(uint64(syscall.f1)))333 v.SetFloat(math.Float64frombits(uint64(syscall.f1)))
359 case reflect.Struct:334 case reflect.Struct:
360- v = getStruct(outType, syscall)335+ v = getStruct(outType, *syscall)
361 default:336 default:
362 panic("purego: unsupported return kind: " + outType.Kind().String())337 panic("purego: unsupported return kind: " + outType.Kind().String())
363 }338 }
364- return []reflect.Value{v}339+ if len(args) > 0 {
340+ // reuse args slice instead of allocating one when possible
341+ args[0] = v
342+ return args[:1]
343+ } else {
344+ return []reflect.Value{v}
345+ }
365 })346 })
366 fn.Set(v)347 fn.Set(v)
367 }348 }
368 349
350+func addValue(v reflect.Value, keepAlive []interface{}, addInt func(x uintptr), addFloat func(x uintptr), addStack func(x uintptr), numInts *int, numFloats *int, numStack *int) []interface{} {
351+ switch v.Kind() {
352+ case reflect.String:
353+ ptr := strings.CString(v.String())
354+ keepAlive = append(keepAlive, ptr)
355+ addInt(uintptr(unsafe.Pointer(ptr)))
356+ case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
357+ addInt(uintptr(v.Uint()))
358+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
359+ addInt(uintptr(v.Int()))
360+ case reflect.Ptr, reflect.UnsafePointer, reflect.Slice:
361+ // There is no need to keepAlive this pointer separately because it is kept alive in the args variable
362+ addInt(v.Pointer())
363+ case reflect.Func:
364+ addInt(NewCallback(v.Interface()))
365+ case reflect.Bool:
366+ if v.Bool() {
367+ addInt(1)
368+ } else {
369+ addInt(0)
370+ }
371+ case reflect.Float32:
372+ addFloat(uintptr(math.Float32bits(float32(v.Float()))))
373+ case reflect.Float64:
374+ addFloat(uintptr(math.Float64bits(v.Float())))
375+ case reflect.Struct:
376+ keepAlive = addStruct(v, numInts, numFloats, numStack, addInt, addFloat, addStack, keepAlive)
377+ default:
378+ panic("purego: unsupported kind: " + v.Kind().String())
379+ }
380+ return keepAlive
381+}
382+
369 // maxRegAllocStructSize is the biggest a struct can be while still fitting in registers.383 // maxRegAllocStructSize is the biggest a struct can be while still fitting in registers.
370 // if it is bigger than this than enough space must be allocated on the heap and then passed into384 // if it is bigger than this than enough space must be allocated on the heap and then passed into
371 // the function as the first parameter on amd64 or in R8 on arm64.385 // the function as the first parameter on amd64 or in R8 on arm64.

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