frankenphp icon indicating copy to clipboard operation
frankenphp copied to clipboard

perf: optimize types

Open AlliBalliBaba opened this issue 3 weeks ago • 1 comments

I had this branch lying around for quite a while waiting for #1894 to be merged. This PR makes type conversions up to 4x faster by minimizing Cgo overhead.

Benchmark Name ns/op main ns/op this PR Faster x times
BenchmarkBool-20 264.5 156.5 1.69
BenchmarkInt-20 311.6 162.6 1.92
BenchmarkFloat-20 367.1 174.4 2.11
BenchmarkString-20 169.5 165.1 ~1
BenchmarkInternedString-20 162.4 160.3 ~1
BenchmarkEmptyMap-20 223.0 98.04 2.27
BenchmarkMap5Entries-20 4229 1710 2.47
BenchmarkAssociativeArray5Entries-20 4088 1684 2.43
BenchmarkSlice5Entries-20 3379 853.2 3.96
BenchmarkMap50Entries-20 38023 13524 2.81

AlliBalliBaba avatar Dec 19 '25 22:12 AlliBalliBaba

@alexandre-daubois I kind of missed this in #1731, but returning the same zval here in integration tests is an invalid operation (because of GC): https://github.com/php/frankenphp/blob/19d00a08e2217167918aa7c32c056c1a70f22021/testdata/integration/callable.go#L35

The array actually needs an additional reference (via GC_ADDREF). Strangely, this was compensated by doing more heap allocations, which potentially hides values from the GC: https://github.com/php/frankenphp/blob/19d00a08e2217167918aa7c32c056c1a70f22021/types.go#L374

Looking at PHPValue(), I think it might make sense to have an alternate version that directly takes the zval as value and just pass the return value directly to go.

func PHPReturnValue(value any, return_value unsafe.Pointer)

That would make memory leaks more unlikely. WDYT?

func go_do_something(param *C.zval, returnValue *C.zval) {

    result := // do something with param

    // instead of allocating a new zval
    // write directly into return_value
    PhpReturnValue(unsafe.Pointer(returnValue), result)
}

AlliBalliBaba avatar Dec 23 '25 22:12 AlliBalliBaba

This branch now also adds a PHPReturnValue function that allows directly writing into a preallocated zval. This avoids memory leaks of the zval container, like the one currently present in external worker tests on main.

AlliBalliBaba avatar Dec 28 '25 21:12 AlliBalliBaba

To be sure, is it just in case of an array or the leak could also be present in other cases? I never saw PHP report leaks because of return values and your comment only mentions arrays, that's why I'm not sure?

If it is only about arrays, maybe we could have the best of both worlds for the DX which is keeping the classical return statement for scalars and maybe a new function to return arrays

alexandre-daubois avatar Dec 29 '25 08:12 alexandre-daubois

The leak happens in tests since PHPValue is used internally in one place. When using PHPValue in most cases the user also needs to efree the actual container, which can be easily forgotten. PHP will report leaks if the debug version is installed like in the dev.Dockerfile.

If it is only about arrays, maybe we could have the best of both worlds for the DX which is keeping the classical return statement for scalars and maybe a new function to return arrays

It's not only about arrays, I think PHPReturnValue might be the only function we need. In the usual function flow there always is a preallocated zval and we just write directly into it.

The api just becomes: (typing this out on mobile)

PHPfunction(somefunction) {

  //... parse args

  go_do_something(args, return_value);

}
func go_do_something(arg *C.zval, returnValue *C.zval) {

  transformedArg = // do something with arg...

  // just write the go value directly into return_value
  frankenphp.PHPReturnValue(unsafe.Pointer(returnValue), transformedArg)

}

No need for RETURN_BOOL, RETURN_ARR, PHPMap, PHPString etc..

AlliBalliBaba avatar Dec 29 '25 10:12 AlliBalliBaba