分配子例程的结果是否会导致数据的副本?

Does assigning the result of a subroutine result in a copy of the data?

提问人:Adam Millerchip 提问时间:12/16/2016 更新时间:12/20/2016 访问量:120

问:

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

sub maketext { 'text' };

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

my $bar_ref = \maketext();

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

我怎样才能说服自己相信它们的等价性或不等价性?

Perl 引用 按值传递

评论

0赞 Georg Mavridis 12/16/2016
即使考虑在这里花费的时间也是一个巨大的错误。您应该考虑哪种代码更具可读性。如果你的结果如此之大,以至于复制确实有真正的影响,你应该返回一个参考。
1赞 Sobrique 12/16/2016
“过早的优化是万恶之源”。想都别想这个,这是微不足道的。除非不是,但为此,请使用代码探查器。
3赞 simbabque 12/16/2016
这不一定是微优化。我不知道答案,我很想知道它是什么,只是因为。
0赞 Adam Millerchip 12/18/2016
上下文:我正在使用一个函数,该函数调用并将结果传递给另一个函数。我想知道我是否纯粹出于兴趣而写作或有所作为。现在我明白了后者会复制标量,但不会复制底层数据,所以差异可以忽略不计。WWW::Mechanize::contentmy $content_ref = \$mech->content; foo($content_ref)my $content = $mech->content; foo(\$content);

答:

4赞 zdim 12/16/2016 #1

数据复制似乎发生

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

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

此打印

SCALAR(0x11f5818)
SCALAR(0x11cfa68)

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

从表面上看,当函数返回时,必须复制数据,并获取对它的引用。

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

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


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

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

或与 ,或在您的情况下与潜艇do { \my $var }

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

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

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

将 this 的返回分配给变量时,不会创建额外的数据副本,因为返回的是引用。

1赞 Borodin 12/16/2016 #2

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

这就是在

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

这引发了错误

尝试修改只读值

3赞 ikegami 12/16/2016 #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

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

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
[...]

评论

0赞 Adam Millerchip 12/18/2016
你的第一句话是“是的,它复制了”,但在阅读了你回答的 COW 部分后,似乎我的问题中“数据”的定义需要更具体。从这个答案中,我了解到标量被复制了,但底层字符串缓冲区没有被复制(直到值被修改)。我从左值子中注意到,虽然它避免了复制,但它会在每次调用时创建一个新的标量(仍然使用相同的 PV),而非左值版本重用相同的变量。
0赞 ikegami 12/19/2016
my在可能的情况下,变量确实会被重用(在子退出时确定)。如果一个变量是从左值子返回的,显然不可能重用它。my
0赞 ikegami 12/21/2016
请注意,COW 相对较新,但在此之前还有另一个优化:从 temps 窃取缓冲区。这意味着它是从子中返回的“临时”,它的缓冲区将被“窃取”而不是复制。临时值是构造值(例如,从串联、子线等获得的值)。