【问题标题】:Passing by Reference (when variable can change, but it does not have to)通过引用传递(当变量可以改变,但不是必须的)
【发布时间】:2013-03-11 06:15:52
【问题描述】:

已经非常彻底地解释了,如果它们是技术原因,您只能在 PHP 中通过引用传递,因为 Copy-On-Write 基本上使性能等效。据我了解,如果它从未改变过,它永远不会复制对象。

但是如果函数确实改变了变量,但你的代码再也没有使用它/不使用任何被改变的部分怎么办?是否更改原始代码对代码无关紧要。是的,PHP 优化器可能会考虑到这种情况,但我没有理由相信它会这样做。

传递单个引用肯定会比复制一个巨大的数组或对象快得多。

那么这是通过引用传递的好情况吗?

例如,假设您传入了一个 DomCrawler(只不过是一个大的 [html 格式] 字符串,但在这种特定情况下它是通过引用隐式传递的)。爬行一点并提取一些信息。在许多情况下,您不需要将 Crawler 重置为其原始位置,因为您根本不会再次使用它。 另外,想象一下我们确实使用了 DOMCrawler,我们从中读取 URI。该函数没有改变这一点,所以通过引用或值传递仍然是等价的,但是通过引用传递不会明显更优化吗?我认为这种情况对于任何优化者来说都很难发现。

【问题讨论】:

  • 那么问题是什么?
  • 你的问题是?你不会怀疑那些设计 PHP 的人吧?因为那太冒昧了;)
  • 我永远不会明白为什么这么讨厌/不理解隐含的问题。更新的问题。
  • “但是如果函数确实改变了变量,但你的代码再也不会使用它/不使用任何被改变的部分”这没有意义。如果您不打算使用已更改的变量,那么为什么首先要在代码中使用它?
  • 您的假设情况(传递DomCrawler)无关紧要,因为在PHP 中对象总是通过引用隐式传递(实际上它并不完全是引用,但足够接近)。传递的对象将永远被复制,除非你明确地clone它。

标签: php pass-by-reference copy-on-write


【解决方案1】:

那么这是通过引用传递的好情况吗?

没有。

好的。想象一下,你有一个 $bigString 并将它传递给一个函数,该函数修改它并用它做一些事情,而调用者再也不需要它了。通过引用传递最初更快,因为它避免了复制。但是,这仍然是个坏主意。

(1) 如果另一个调用者调用您的函数确实想要继续使用该变量,事情就会中断。该引用基本上违反了封装。

(2) 只要​​在函数之外有 超过 1 个非引用变量引用该值,只需创建引用就需要再次复制。 (变量值保存在可能是非引用(修改时复制)或引用(修改时不做任何特殊操作)的容器中,因此对于引用变量和非引用变量,请尝试在同时,它必须被复制。)

(3) 由于上述原因,像在函数中调用 strlen 这样无辜的事情将不得不复制值,因为 strlen 的参数是按值传递的,这是常态。现在想象一下,您在一个循环中调用了一些函数,例如 substr 和 strlen,并且您每次都在制作数据的新副本。

(4) DDR3 RAM 每秒可以推动超过 10 GB 的速度,而 CPU 缓存 RAM 天知道有多快。我认为 PHP 性能需要担心的事情比字符串或数组复制需要多长时间。

不要使用参考来迷信性能提升。它永远不会起作用。

如果你真的想避免复制,那么正确的做法可能是将你的函数作为一个照顾变量的对象的方法:

class Thing {
    private $bigString;

    public function foo() {
        $this->bigString[0] = 'x';
    }
}

那么您就可以避免复制,获得封装的好处,并且没有引用的微妙之处。

PS:DomCrawler 不是一个很好的例子,因为它是一个对象。 PHP 对象从来都不是写时复制(我认为它们是,但是有一个额外的间接级别,所以写时复制的唯一部分是一个小指针容器,或类似的东西)。

【讨论】:

  • 字符串可以实现为指向底层不可变字符数据的指针。那么复制字符串只会复制一个指针,并不涉及复制大量“数据”。
  • @newacct 它确实涉及它。 PHP 字符串是可变的,所以引用和按值赋值不能共享同一个数据块。
  • 值类型在语义上等同于不可变引用类型。更改字符串的字符数据的唯一方法是为其分配一个新字符串。告诉我,这与 Java 中的字符串有何不同?
  • @newacct "更改字符串字符数据的唯一方法是为其分配一个新字符串。"在 Java 中这是正确的,因为 char[] 字段是私有的,您无法访问它。在 PHP 中,您可以执行(如在 C 中)$str[0] = 'a';,它只会更改开头的一个字节。如果字符串被多个变量指向并且它们不是引用,则该分配只需要复制其余的内存,因为它需要复制它以兑现早期的按值分配。
  • 但它可以像我说的那样实现——任何修改都涉及分配一个从头开始创建的新字符串。它的实际实现方式取决于实现。
【解决方案2】:

我一直避免通过引用传递,原因与我避免 goto 的原因相同。

$a = myFunction($a);

myFunction(&$a);更容易阅读和重用

【讨论】:

  • myFunction(&$a) 是调用时通过引用传递的,已被弃用,并且从未被推荐。真正的传递引用在函数声明中指定。
  • @newacct 写起来更短,并且得到了相同的观点。无论如何,这一切都取决于个人喜好。我从不关心通过引用传递,因为它使变量的值被设置的确切位置不太清楚。
【解决方案3】:

根据我对PHP系统的理解,一切都是通过“引用”来传递的。因此,如果您要传递巨大的数组或对象,它们总是通过“引用”传递。

我把“参考”放在引号中,因为这里有两种不同的类型:

  • 显式引用是您向 php 指定您希望将其作为引用跟踪的位置
  • 隐式引用是您希望将其作为值而不是跟踪的位置

PHP 默认为隐式引用。

因此,在您更改隐式引用之前,不会影响性能。在这种情况下,PHP 会将值分配到不同的内存地址并更新您的引用。

如果编译器检测到该变量不再被使用或不再在作用域内,GC 会将其铲除。

【讨论】:

    猜你喜欢
    • 2012-02-14
    • 2011-02-27
    • 2011-08-16
    • 2013-11-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-16
    • 2011-09-16
    相关资源
    最近更新 更多