【问题标题】:Best way to iterate through a Perl array遍历 Perl 数组的最佳方法
【发布时间】:2012-05-16 06:28:39
【问题描述】:

遍历 Perl 数组的最佳实现是(就速度和内存使用而言)?有没有更好的办法? (@Array 不需要保留)。

实施1

foreach (@Array)
{
      SubRoutine($_);
}

实施 2

while($Element=shift(@Array))
{
      SubRoutine($Element);
}

实施 3

while(scalar(@Array) !=0)
{
      $Element=shift(@Array);
      SubRoutine($Element);
}

实施 4

for my $i (0 .. $#Array)
{
      SubRoutine($Array[$i]);
}

实施 5

map { SubRoutine($_) } @Array ;

【问题讨论】:

  • 为什么会有“最佳”?特别是考虑到我们不知道您将如何衡量一个与另一个(速度比内存使用更重要?是map 和可接受的答案?等等)
  • 你发布的三个中有两个会让我去“WTH?!”除非有额外的周围环境使它们成为明智的选择。无论如何,这个问题属于“两个数字相加最好的方法是什么?”大多数时候,只有一种方法。然后,在某些情况下,您需要一种不同的方式。投票结束。
  • @SinanÜnür 我赞同你的观点(只有一种方法可以将两个数字相加),但是这个类比不够强,不能轻视。显然,方法不止一种,OP 想要了解什么是好主意,什么不是。
  • Programming Perl 第三版的第 24 章有一节关于效率的内容值得一读。它解决了不同类型的效率,例如时间、程序员、维护者。该部分以“请注意,优化时间有时可能会降低空间或程序员效率的成本(由下面的冲突提示表示)开始。他们是休息时间。”
  • 两个数字相加的一种方法?如果您研究较低级别的调用/实现,则不会……考虑进行前瞻,进行保存加法器等。

标签: arrays perl iteration


【解决方案1】:
  • 在速度方面:#1 和 #4,但在大多数情况下不会太多。

    您可以编写一个基准来确认,但我怀疑您会发现 #1 和 #4 会稍微快一些,因为迭代工作是在 C 而不是 Perl 中完成的,并且不会发生不必要的数组元素复制。 ($_aliased 到 #1 中的元素,但 #2 和 #3 实际上 复制 数组中的标量。)

    #5 可能类似。

  • 就内存使用而言:除了 #5 之外,它们都相同。

    for (@a) 是特殊情况以避免数组变平。循环遍历数组的索引。

  • 在可读性方面:#1。

  • 在灵活性方面:#1/#4 和 #5。

    #2 不支持错误的元素。 #2 和 #3 具有破坏性。

【讨论】:

  • 哇,您用简短的句子添加了大量信息。
  • #2 在你做队列时很好(例如广度优先搜索):my @todo = $root; while (@todo) { my $node = shift; ...; push @todo, ...; ...; }
  • 实现 4 是否不会创建一个中间索引数组,这可能会引入大量要使用的内存?如果是这样,听起来不应该使用这种方法。 stackoverflow.com/questions/6440723/…rt.cpan.org/Public/Bug/Display.html?id=115863
  • @ikegami 忠于你的冠军风格 - 很好的答案:)
【解决方案2】:

如果你只关心@Array的元素,使用:

for my $el (@Array) {
# ...
}

如果索引很重要,请使用:

for my $i (0 .. $#Array) {
# ...
}

或者,从perl 5.12.1 开始,您可以使用:

while (my ($i, $el) = each @Array) {
# ...
}

如果你需要循环体中的元素和它的索引,我希望使用each 是最快的,但是你会放弃与 pre-5.12.1 perls 的兼容性。

在某些情况下,其他一些模式可能更合适。

【讨论】:

  • 我希望each 是最慢的。它完成了其他人的所有工作,减去一个别名、一个列表分配、两个标量副本和两个标量清除。
  • 而且,就我的测量能力而言,你是对的。使用for 迭代数组的索引大约快 45%,迭代数组引用的索引时快 20%(我在正文中访问 $array->[$i]),超过使用 each 和 @987654332 @.
【解决方案3】:

IMO,实现#1 是典型的,对于 Perl 来说,它的简短和惯用仅在这一点上就胜过其他实现。至少,这三种选择的基准可以让您深入了解速度。

【讨论】:

    【解决方案4】:

    1 与 2 和 3 大不相同,因为它使数组保持完整,而其他两个则保持空。

    我会说#3 很古怪,而且可能效率较低,所以算了吧。

    这让你有了#1 和#2,它们做的事情不一样,所以一个不能比另一个“更好”。如果数组很大并且您不需要保留它,一般范围会处理它(但请参阅 注意),所以 一般来说,#1 仍然是最清晰和最简单的方法。关闭每个元素不会加快任何速度。即使需要从引用中释放数组,我也会去:

    undef @Array;
    

    完成后。

    • 注意:包含数组范围的子程序实际上保留了数组并在下次重新使用空间。 一般,应该没问题(见 cmets)。

    【讨论】:

    • @Array = (); 不会释放底层数组。即使超出范围也不会这样做。如果你想释放底层数组,你可以使用undef @Array;
    • 演示; perl -MDevel::Peek -e'my @a; Dump(\@a,1); @a=qw( a b c ); Dump(\@a,1); @a=(); Dump(\@a,1); undef @a; Dump(\@a,1);' 2>&1 | grep ARRAY
    • WHAT??? 我原以为 GC 的重点是一旦 ref count == 0,所涉及的内存就可以回收了。
    • @ikegami:我看到了关于()undef 的事情,但是如果超出范围并不会释放该范围内的本地数组使用的内存,那不是perl泄漏的灾难?这不可能是真的。
    • 它们也不会泄漏。 sub 仍然拥有它们,并将在下次调用 sub 时重用它们。针对速度进行了优化。
    【解决方案5】:

    决定此类问题以对其进行基准测试的最佳方法:

    use strict;
    use warnings;
    use Benchmark qw(:all);
    
    our @input_array = (0..1000);
    
    my $a = sub {
        my @array = @{[ @input_array ]};
        my $index = 0;
        foreach my $element (@array) {
           die unless $index == $element;
           $index++;
        }
    };
    
    my $b = sub {
        my @array = @{[ @input_array ]};
        my $index = 0;
        while (defined(my $element = shift @array)) {
           die unless $index == $element;
           $index++;
        }
    };
    
    my $c = sub {
        my @array = @{[ @input_array ]};
        my $index = 0;
        while (scalar(@array) !=0) {
           my $element = shift(@array);
           die unless $index == $element;
           $index++;
        }
    };
    
    my $d = sub {
        my @array = @{[ @input_array ]};
        foreach my $index (0.. $#array) {
           my $element = $array[$index];
           die unless $index == $element;
        }
    };
    
    my $e = sub {
        my @array = @{[ @input_array ]};
        for (my $index = 0; $index <= $#array; $index++) {
           my $element = $array[$index];
           die unless $index == $element;
        }
    };
    
    my $f = sub {
        my @array = @{[ @input_array ]};
        while (my ($index, $element) = each @array) {
           die unless $index == $element;
        }
    };
    
    my $count;
    timethese($count, {
       '1' => $a,
       '2' => $b,
       '3' => $c,
       '4' => $d,
       '5' => $e,
       '6' => $f,
    });
    

    并在为 x86_64-linux-gnu-thread-multi 构建的 perl 5 版本 24 subversion 1 (v5.24.1) 上运行它

    我明白了:

    Benchmark: running 1, 2, 3, 4, 5, 6 for at least 3 CPU seconds...
             1:  3 wallclock secs ( 3.16 usr +  0.00 sys =  3.16 CPU) @ 12560.13/s (n=39690)
             2:  3 wallclock secs ( 3.18 usr +  0.00 sys =  3.18 CPU) @ 7828.30/s (n=24894)
             3:  3 wallclock secs ( 3.23 usr +  0.00 sys =  3.23 CPU) @ 6763.47/s (n=21846)
             4:  4 wallclock secs ( 3.15 usr +  0.00 sys =  3.15 CPU) @ 9596.83/s (n=30230)
             5:  4 wallclock secs ( 3.20 usr +  0.00 sys =  3.20 CPU) @ 6826.88/s (n=21846)
             6:  3 wallclock secs ( 3.12 usr +  0.00 sys =  3.12 CPU) @ 5653.53/s (n=17639)
    

    所以“foreach (@Array)”的速度大约是其他的两倍。其他都非常相似。

    @ikegami 还指出,除了速度之外,这些实现还有很多不同。

    【讨论】:

    • 比较$index &lt; $#array实际上应该是$index &lt;= $#array,因为$#array不是数组的长度,而是它的最后一个索引。
    【解决方案6】:

    单行打印元素或数组。

    为 (@array) 打印 $_;

    注意:请记住 $_ 在内部引用循环中的 @array 元素。 $_ 中所做的任何更改都将反映在@array 中; 前任。

    my @array = qw( 1 2 3 );
    for (@array) {
            $_ = $_ *2 ;
    }
    print "@array";
    

    输出:2 4 6

    【讨论】:

      猜你喜欢
      • 2016-01-14
      • 1970-01-01
      • 2010-10-04
      • 1970-01-01
      • 2021-12-12
      • 2018-03-09
      • 1970-01-01
      • 2021-03-28
      • 2014-01-07
      相关资源
      最近更新 更多