如何从 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
内容。相反,我会增加myArrayProperty
的refcount
并返回指向它的指针。这与在 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 扩展返回数组,而不将其复制到内存中?的主要内容,如果未能解决你的问题,请参考以下文章