【问题标题】:How to return array from a PHP extension, without copying it in memory?如何从 PHP 扩展返回数组,而不将其复制到内存中?
【发布时间】: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;
}

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

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

【问题讨论】:

    标签: php c php-extension php-internals


    【解决方案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。问题如上所述:对象和调用者引用不同的内存位置。
      猜你喜欢
      • 2016-07-04
      • 1970-01-01
      • 1970-01-01
      • 2019-09-05
      • 2020-11-19
      • 2014-07-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多