Home » Php » c – How to return array from a PHP extension, without copying it in memory?

c – How to return array from a PHP extension, without copying it in memory?

Posted by: admin July 12, 2020 Leave a comment

Questions:

I’m developing a PHP-extension, where an object method needs to return an array zval.

The method looks like:

ZEND_METHOD(myObject, myMethod)
{
    zval **myArrayProperty;
    if (zend_hash_find(Z_OBJPROP_P(getThis()), "myArrayProperty", sizeof("myArrayProperty"), (void **) &myArrayProperty) == FAILURE) {
        RETURN_FALSE;
    }
    RETURN_ZVAL(*myArrayProperty, 1, 0);
}

The code works fine and does the expected thing – it returns object’s myArrayProperty. However, I’d like to optimize the process.

myArrayProperty stores an array, which can be quite big. And the RETURN_ZVAL() macro duplicates that array in order to return the value. The duplication process takes good amount of time to acquire memory and copy all the array values. At the same time, the returned array is usually used for read-only operations. So a nice optimization would be to use PHP’s mechanism with reference counting and do not duplicate myArrayProperty contents. Rather I’d increase refcount of myArrayProperty and just return pointer to it. This is the same tactic as usually used, when working with variables in a PHP extension.

However, seems, there is no way to do it – you have to duplicate value in order to return it from a PHP extension function. Changing function signature to return value by reference, is not an option, because it links the property and returned value – i.e. changing returned value later, changes the property as well. That is not an acceptable behavior.

The inability to engage reference counting looks strange, because same code in PHP:

function myMethod() {
{
    return $this->myArrayProperty;
}

is optimized by the reference counting mechanism. That’s why I’m asking this question at StackOverflow in case I missed something.

So, is there a way to return an array from a function in PHP extension, without copying the array in memory?

How to&Answers:

If your function returns by-value this is only possible as of PHP 5.6 (current master) using the RETURN_ZVAL_FAST macro:

RETURN_ZVAL_FAST(*myArrayProperty);

If your function returns by-reference (return_reference=1 in the arginfo) you can return using the following code:

zval_ptr_dtor(&return_value);
SEPARATE_ZVAL_TO_MAKE_IS_REF(myArrayProperty);
Z_ADDREF_PP(myArrayProperty);
*return_value_ptr = *myArrayProperty;

If your function returns by-value and you’re on PHP 5.5 or older you can still optimize the refcount=1 case:

if (Z_REFCOUNT_PP(myArrayProperty) == 1) {
    RETVAL_ZVAL(*myArrayProperty, 0, 1);
    Z_ADDREF_P(return_value);
    *myArrayProperty = return_value;
} else {
    RETVAL_ZVAL(*myArrayProperty, 1, 0);
}

Answer:

I don’t have access to PHP < 5.6 but I think the problem is that the value is copied only. To be absolutely sure you should search the code for the defines in question.

That means you might be able to try:

 zval *arr;
 MAKE_STD_ZVAL(arr);
 array_init(arr);
 // Do things to the array.
 RETVAL_ZVAL(arr, 0, 0);
 efree(arr);

This is dangerous if used unwisely. If used with your own temporary containers I don’t know of any problems.

You can also probably work on return value directly which might be a better approach. You would likely initialise it and pass it around as a pointer at the start.

You can wrap your return result like this. You can also experiment with references.

Answer:

It’s been awhile, since I coded something like this…

So, what I do in code below: 1). explicitly increasing refcounter 2). returning zval without copying it

ZEND_METHOD(myObject, myMethod)
{
    zval **myArrayProperty;

    if (zend_hash_find(Z_OBJPROP_P(getThis()), "myArrayProperty", sizeof("myArrayProperty"), (void **) &myArrayProperty) == FAILURE) {
        RETURN_FALSE;
    }

    Z_ADDREF_PP(myArrayProperty);
    RETURN_ZVAL(*myArrayProperty, 0, 0);
}