如何从 PHP 扩展返回数组,而不将其复制到内存中?

Posted

技术标签:

【中文标题】如何从 PHP 扩展返回数组,而不将其复制到内存中?【英文标题】:How to return array from a PHP extension, without copying it in memory? 【发布时间】:2013-07-24 12:59:01 【问题描述】:

我正在开发一个 php 扩展,其中一个对象方法需要返回一个数组 zval

方法如下:

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);

代码工作正常,并完成了预期的事情——它返回对象的myArrayProperty。但是,我想优化这个过程。

myArrayProperty 存储一个数组,这个数组可能很大。 RETURN_ZVAL() 宏复制该数组以返回值。复制过程需要大量时间来获取内存并复制所有数组值。同时,返回的数组通常用于只读操作。所以一个很好的优化是使用 PHP 的引用计数机制并且不重复 myArrayProperty 内容。相反,我会增加myArrayPropertyrefcount 并返回指向它的指针。这与在 PHP 扩展中处理变量时通常使用的策略相同。

但是,似乎没有办法做到这一点 - 您必须复制值才能从 PHP 扩展函数返回它。将函数签名更改为通过引用返回值不是一种选择,因为它将属性和返回值联系起来 - 即稍后更改返回值,也会更改属性。这是不可接受的行为。

无法进行引用计数看起来很奇怪,因为 PHP 中的代码相同:

function myMethod() 

    return $this->myArrayProperty;

通过引用计数机制进行了优化。这就是我在 *** 上问这个问题的原因,以防我错过了什么。

那么,有没有办法从 PHP 扩展中的函数返回一个数组,而无需将数组复制到内存中?

【问题讨论】:

【参考方案1】:

如果您的函数按值返回,这只能从 PHP 5.6(当前主版本)开始使用 RETURN_ZVAL_FAST 宏:

RETURN_ZVAL_FAST(*myArrayProperty);

如果您的函数按引用返回(arginfo 中的return_reference=1),您可以使用以下代码返回:

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

如果您的函数按值返回并且您使用的是 PHP 5.5 或更早版本,您仍然可以优化 refcount=1 案例:

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);

【讨论】:

好吧,正如描述中所说 - 该函数不会通过引用返回值。因此,不幸的是,这不是一个解决方案。 对不起,我错过了。在这种情况下,你想要的是不可能的。 虽然我看不出一个直接的原因为什么我们不能在没有 ACC_RETURN_REFERENCE 的情况下传入 return_value_ptr (除了这将允许您从非引用返回 is_ref=1 zval功能)。您可能想在 internals@ 上询问此问题。 它不起作用,因为return_value_ptr只有在函数/方法声明引用返回时才由引擎初始化。 @AndreyTserkus 我知道 :) 只是说这是我们可能想要改变的。我不清楚为什么我们不能总是设置 return_value_ptr。【参考方案2】:

我无法访问 PHP

这意味着你也许可以尝试:

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

如果使用不当会很危险。如果与您自己的临时容器一起使用,我不知道有任何问题。

您也可以直接处理返回值,这可能是一种更好的方法。您可能会初始化它并在开始时将其作为指针传递。

您可以像这样包装您的返回结果。您还可以尝试参考。

【讨论】:

【参考方案3】:

已经有一段时间了,因为我编写了这样的代码......

那么,我在下面的代码中做了什么:1)。显式增加 refcounter 2)。返回 zval 而不复制它

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);

【讨论】:

但这不会导致内存泄漏或段错误(以先到者为准)吗?当所有对属性的引用都被清除时,会发生内存泄漏,但无法释放其 zval 容器占用的内存,因为 refcount 仍将保持为 1。当返回值被释放时,将发生 Segfault,因此它的 zval container 和数组一起被清空(共享 HashTable,既从该容器引用,又从属性 zval 容器引用),所以稍后使用属性会导致不可预知的,但肯定是错误的效果。 属性引用 zval — 即 refcount=1。此代码增加了 refcount,因为 zval 被返回并将被对象和调用者引用。如果对象不需要属性,它将减少引用计数,因此它将仅由调用者拥有。所以,这段代码对我来说看起来很理智。但是,再一次,所有这些都只是理论上的——我现在什至没有解压 PHP 的源代码 不幸的是,正如预测的那样,代码不起作用 - 在实践中得到证实:pastebin.com/FRfaJZvL。问题如上所述:对象和调用者引用不同的内存位置。

以上是关于如何从 PHP 扩展返回数组,而不将其复制到内存中?的主要内容,如果未能解决你的问题,请参考以下文章

如何在项目中包含/引用文件而不将其复制到项目目录?

如何逐行读取大型文本文件,而不将其加载到内存中?

如何直接保存到持久存储,而不将数据保存到内存中

R:函数如何使用省略号 (...) 接受变量参数而不将它们复制到内存中?

如何将值添加到数组中而不将其在for循环外重置为0

如何播放音频样本而不将其写入文件?