【问题标题】:Does assigning the result of a subroutine result in a copy of the data?分配子程序的结果是否会导致数据副本?
【发布时间】:2017-05-01 22:55:43
【问题描述】:

我试图了解分配子例程的结果是否会导致复制该数据。

sub maketext { 'text' };

my $foo = maketext();
my $foo_ref = \$foo;

my $bar_ref = \maketext();

在上面的例子中,$foo_ref 的创建会比$bar_ref 的创建多一个副本吗?

我如何让自己相信它们的等价或不等价?

【问题讨论】:

  • 即使考虑在这里花费的时间也是一个巨大的错误。您应该考虑哪个代码更具可读性。如果您的结果如此巨大,复制确实会产生实际影响,您应该返回一个参考。
  • “过早的优化是万恶之源”。想都别想了,小事一桩。除非不是,但为此您使用代码分析器。
  • 不一定是微优化。我不知道答案,我很想知道它是什么,只是因为。
  • 上下文:我正在使用一个名为 WWW::Mechanize::content 的函数并将结果传递给另一个函数。我想知道我写my $content_ref = \$mech->content; foo($content_ref)my $content = $mech->content; foo(\$content); 是否会有所作为,纯粹是出于兴趣。现在我明白后者会复制标量,但不会复制基础数据,因此差异可以忽略不计。

标签: perl reference pass-by-value


【解决方案1】:

数据复制似乎发生了

sub maketext {
    my $text = 'text';
    say \$text;
    return $text;
}

my $bar_ref = \maketext();
say $bar_ref;

打印出来

标量(0x11f5818) 标量(0x11cfa68)

sub中创建的数据地址和$bar_ref指向的地址不一样。

从表面上看,当函数返回时,数据必须被复制,并且引用它。

另一种可能性是对原始数据的引用即使超出范围也会保留,就像它发生在闭包中一样。然而,这里的函数返回 first 然后它的返回被操纵。所以我看不出任何机制如何知道对数据做了什么,所以数据被适当地复制了。

您正在创建一个匿名标量引用,但在函数返回之外。


一种在没有相应变量的情况下创建对标量的引用的方法是

my $scalar_ref = \do { my $var };

或者do { \my $var },或者在你的情况下使用一个子

sub anon_scalar_ref {
    # ...
    return \my $var;
}

但是,我看不出你有什么用。也许你想做

sub maketext {
    # ... define $text ...
    return \$text;
}

当您将 this 的返回值分配给变量时,不会制作额外的数据副本,因为它是返回的引用。

【讨论】:

    【解决方案2】:

    Perl 必须复制数据。否则,对变量$foo 的任何后续修改都将试图修改字符串常量'text',这将导致您的代码死机。

    这就是发生的事情

    for ( 'text' ) {
        $_ = 'test';
    }
    

    引发错误

    尝试修改只读值

    【讨论】:

      【解决方案3】:

      是的,它会复制。

      use Devel::Peek qw( Dump );
      
      sub maketext {
          my $text = 'text';
          Dump($text);
          return $text;
      }
      
      my $ref = \maketext();
      Dump($$ref);
      

      输出:

      SV = PV(0x8b18a0) at 0x8dbe38   <-- $text is at 0x8dbe38
        REFCNT = 1
        FLAGS = (POK,IsCOW,pPOK)
        PV = 0x8d9f70 "text"\0        <-- String buffer at 0x8d9f70
        CUR = 4
        LEN = 10
        COW_REFCNT = 1
      SV = PV(0x8b1920) at 0x8b0cc8   <-- $$ref is at 0x8b0cc8
        REFCNT = 1
        FLAGS = (POK,IsCOW,pPOK)
        PV = 0x8d9f70 "text"\0        <-- String buffer at 0x8d9f70
        CUR = 4
        LEN = 10
        COW_REFCNT = 1
      

      但是,由于写入时复制 (COW) 功能,不会复制字符串缓冲区。事实上,出于同样的原因,当您执行my $text = 'text'; 时,它甚至没有被复制。这意味着常量 $text$$ref 都共享同一个字符串缓冲区(直到编辑其中一个字符串缓冲区),即使它们是完全不同的标量。

      您可以避免使用左值子复制返回值。

      use Devel::Peek qw( Dump );
      
      sub maketext :lvalue {
          my $text = 'text';
          Dump($text);
          return $text;
      }
      
      my $ref = \maketext();
      Dump($$ref);
      

      输出:

      SV = PV(0xe43c80) at 0xe6e238
      [...]
      SV = PV(0xe43c80) at 0xe6e238
      [...]
      

      【讨论】:

      • 您的第一句话是“是的,它复制了”,但是在阅读了您答案的 COW 部分之后,似乎我的问题中“数据”的定义需要更具体。从这个答案我明白 scalar 被复制,但不是底层字符串缓冲区(直到值被修改)。我从左值子中注意到,虽然它避免了复制,但它会在每次调用时创建一个新的标量(仍然使用相同的 PV),而非左值版本重用相同的变量。
      • my 变量确实在可能的情况下被重用(在子退出时确定)。如果 my 变量从左值子返回,显然不可能重用它。
      • 请注意,COW 相对较新,但在此之前还有另一个优化:从 temps 窃取缓冲区。这意味着它是从子返回的“临时”,它的缓冲区将被“窃取”而不是复制。临时值是一个构造值(例如,从连接、substr 等获得的值)。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-07-21
      • 2016-10-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多