From 0196f2d3cc8275725fe6476b32409caa244b1a8a Mon Sep 17 00:00:00 2001 From: TotallyGamerJet Date: Thu, 17 Oct 2024 22:31:58 -0400 Subject: [PATCH] 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 --- func.go | 104 ++++++++++++++++++++++++++++++++------------------------ 1 file changed, 59 insertions(+), 45 deletions(-) diff --git a/func.go b/func.go index a8a3d620..7cb91a8f 100644 --- a/func.go +++ b/func.go @@ -10,11 +10,16 @@ import ( "math" "reflect" "runtime" + "sync" "unsafe" "github.com/ebitengine/purego/internal/strings" ) +var thePool = sync.Pool{New: func() any { + return new(syscall15Args) +}} + // RegisterLibFunc is a wrapper around RegisterFunc that uses the C function returned from Dlsym(handle, name). // It panics if it can't find the name symbol. func RegisterLibFunc(fptr interface{}, handle uintptr, name string) { @@ -200,18 +205,6 @@ func RegisterFunc(fptr interface{}, cfn uintptr) { } } v := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) { - if len(args) > 0 { - if variadic, ok := args[len(args)-1].Interface().([]interface{}); ok { - // subtract one from args bc the last argument in args is []interface{} - // which we are currently expanding - tmp := make([]reflect.Value, len(args)-1+len(variadic)) - n := copy(tmp, args[:len(args)-1]) - for i, v := range variadic { - tmp[n+i] = reflect.ValueOf(v) - } - args = tmp - } - } var sysargs [maxArgs]uintptr stack := sysargs[numOfIntegerRegisters():] var floats [numOfFloats]uintptr @@ -260,7 +253,8 @@ func RegisterFunc(fptr interface{}, cfn uintptr) { runtime.KeepAlive(keepAlive) runtime.KeepAlive(args) }() - var syscall syscall15Args + syscall := thePool.Get().(*syscall15Args) + defer thePool.Put(syscall) if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct { outType := ty.Out(0) if runtime.GOARCH == "amd64" && outType.Size() > maxRegAllocStructSize { @@ -276,40 +270,21 @@ func RegisterFunc(fptr interface{}, cfn uintptr) { } } } - for _, v := range args { - switch v.Kind() { - case reflect.String: - ptr := strings.CString(v.String()) - keepAlive = append(keepAlive, ptr) - addInt(uintptr(unsafe.Pointer(ptr))) - case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - addInt(uintptr(v.Uint())) - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - addInt(uintptr(v.Int())) - case reflect.Ptr, reflect.UnsafePointer, reflect.Slice: - // There is no need to keepAlive this pointer separately because it is kept alive in the args variable - addInt(v.Pointer()) - case reflect.Func: - addInt(NewCallback(v.Interface())) - case reflect.Bool: - if v.Bool() { - addInt(1) - } else { - addInt(0) + for i, v := range args { + if variadic, ok := args[i].Interface().([]interface{}); ok { + if i != len(args)-1 { + panic("purego: can only expand last parameter") } - case reflect.Float32: - addFloat(uintptr(math.Float32bits(float32(v.Float())))) - case reflect.Float64: - addFloat(uintptr(math.Float64bits(v.Float()))) - case reflect.Struct: - keepAlive = addStruct(v, &numInts, &numFloats, &numStack, addInt, addFloat, addStack, keepAlive) - default: - panic("purego: unsupported kind: " + v.Kind().String()) + for _, x := range variadic { + keepAlive = addValue(reflect.ValueOf(x), keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack) + } + continue } + keepAlive = addValue(v, keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack) } if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" { // Use the normal arm64 calling convention even on Windows - syscall = syscall15Args{ + *syscall = syscall15Args{ cfn, sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5], sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11], @@ -317,7 +292,7 @@ func RegisterFunc(fptr interface{}, cfn uintptr) { floats[0], floats[1], floats[2], floats[3], floats[4], floats[5], floats[6], floats[7], syscall.arm64_r8, } - runtime_cgocall(syscall15XABI0, unsafe.Pointer(&syscall)) + runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall)) } else { // This is a fallback for Windows amd64, 386, and arm. Note this may not support floats 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) { // On 32bit platforms syscall.r2 is the upper part of a 64bit return. v.SetFloat(math.Float64frombits(uint64(syscall.f1))) case reflect.Struct: - v = getStruct(outType, syscall) + v = getStruct(outType, *syscall) default: panic("purego: unsupported return kind: " + outType.Kind().String()) } - return []reflect.Value{v} + if len(args) > 0 { + // reuse args slice instead of allocating one when possible + args[0] = v + return args[:1] + } else { + return []reflect.Value{v} + } }) fn.Set(v) } +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{} { + switch v.Kind() { + case reflect.String: + ptr := strings.CString(v.String()) + keepAlive = append(keepAlive, ptr) + addInt(uintptr(unsafe.Pointer(ptr))) + case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + addInt(uintptr(v.Uint())) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + addInt(uintptr(v.Int())) + case reflect.Ptr, reflect.UnsafePointer, reflect.Slice: + // There is no need to keepAlive this pointer separately because it is kept alive in the args variable + addInt(v.Pointer()) + case reflect.Func: + addInt(NewCallback(v.Interface())) + case reflect.Bool: + if v.Bool() { + addInt(1) + } else { + addInt(0) + } + case reflect.Float32: + addFloat(uintptr(math.Float32bits(float32(v.Float())))) + case reflect.Float64: + addFloat(uintptr(math.Float64bits(v.Float()))) + case reflect.Struct: + keepAlive = addStruct(v, numInts, numFloats, numStack, addInt, addFloat, addStack, keepAlive) + default: + panic("purego: unsupported kind: " + v.Kind().String()) + } + return keepAlive +} + // maxRegAllocStructSize is the biggest a struct can be while still fitting in registers. // if it is bigger than this than enough space must be allocated on the heap and then passed into // the function as the first parameter on amd64 or in R8 on arm64.