gpt4 book ai didi

perl - 证明在循环中使用范围运算符不会占用额外的内存

转载 作者:行者123 更新时间:2023-12-04 00:45:07 25 4
gpt4 key购买 nike

Range operator .. 的当前文档声明它不会烧毁 counting loops 的内存:

... The range operator is useful for writing foreach (1..10) loops and for doing slice operations on arrays. In the current implementation, no temporary array is created when the range operator is used as the expression in foreach loops, but older versions of Perl might burn a lot of memory when you write something like this:

    1.   for (1 .. 1_000_000) {
2. # code
3. }


由于上述 for (MIN .. MAX) 的早期实现,我仍然遇到对使用计数循环持谨慎态度的专家,因为他们认为它相当于:
my @temp_array = (MIN .. MAX);       # Needlessly using up memory
for (@temp_array) {

与更具逻辑性和内存效率的相比:
for ($_ = MIN; $_ <= MAX; $_++) {    # Logical counting from MIN to MAX

问题:
  • 有没有一种方法可以证明计数循环不会浪费内存?
  • 有谁知道哪个版本的 Perl 有内存问题以及何时修复?

  • 我能够向自己证明计数循环不会使用下面的单行代码浪费内存,如果它实际上是在创建一个临时数组,这肯定会使我的系统崩溃。然而,如果有关于这个主题的更多决定性信息,这样我们就可以结束这个老太太的故事,那就太好了。
    $ perl -e 'for (1 .. 1_000_000_000_000_000) { print "$_\n"; last if $_ == 5 }'
    1
    2
    3
    4
    5

    解决方案

    以下三个答案中的每一个都可以解释这个问题:
  • ikegamianswer彻底分解不同类型的循环并演示计数循环在前端和后端有何不同。 ☆
  • friedoanswer展示如何使用 top监控内存使用情况。
  • Borodinanswer解决了我关于何时不再成为问题的查询的第二部分:
  • perlop v5.4_68 (1998 年 6 月 23 日发布)警告使用了临时数组。
  • perlop v5.4_69 (1998 年 6 月 29 日发布)声明不再使用临时数组。
  • perldelta v5.4_71 (1998 年 7 月 9 日发布)指出计数循环已优化。

  • 我可能会在某个时候做一些特定的版本测试,但鉴于这显然是一个 16 岁的问题,我相信 perlop 中的警告。可以休息了。

    最佳答案

    首先,这里列出了不同类型的for循环和可以应用的优化。所有这些都存在于从 5.6 到 5.20(现在)的每个 Perl 版本中,我相信它是全面的。

  • for (EXPR; EXPR; EXPR) ⇒ “C 风格的 for 循环”,一个增强的 while 循环。
  • for (EXPRX..EXPRY) ⇒ 一个范围和其他任何东西都被优化为一个计数循环。
  • for (@ARRAY) ⇒ 一个数组而不是其他任何东西被直接访问而不是被展平。
  • for (reverse LIST) ⇒ 以上优化均不适用,但列表是反向遍历而不是反向遍历。
  • for (LIST) ⇒ 在通用的 foreach 循环中,LIST在循环开始之前计算表达式。

  • CONSTX..CONSTY被展平(即除 for (CONSTX..CONSTY) 之外的任何地方),它在编译时而不是运行时被展平。

    黑匣子证明

    基线内存使用情况:
    $ perl -e'system(ps, ho, rss, 0+$$);'
    1540 # 1.5 MiB

    一般情况趋于平缓。
    $ perl -e'$y=2_000_000; for ((),1..$y) { system(ps, ho, rss, 0+$$); last }'
    80208 # 78 MiB

    或者更糟。 (除了正常的堆栈使用之外,它在编译时会扁平化为一个数组。)
    $ perl -e'for ((),1..2_000_000) { system(ps, ho, rss, 0+$$); last }'
    143224 # 140 MiB
    for (CONST..CONST)不会变平。
    $ perl -e'for (1..2_000_000) { system(ps, ho, rss, 0+$$); last }'
    1540 # 1.5 MiB

    事实上, for (EXPR..EXPR)一般不会变平。
    $ perl -e'$y=2_000_000; for (1..$y) { system(ps, ho, rss, 0+$$); last }'
    1540 # 1.5 MiB

    即使没有工具,您也可以分辨出编译时间的差异。
    $ time perl -c -e'1 for 1..2_000_000'
    -e syntax OK

    real 0m0.010s
    user 0m0.004s
    sys 0m0.000s

    $ time perl -c -e'1 for (),1..2_000_000'
    -e syntax OK

    real 0m1.197s
    user 0m0.952s
    sys 0m0.232s

    白盒证明

    未优化的情况在列表上下文中使用范围运算符。内存中的完整列表。
    $ perl -MO=Concise,-exec -e'$y=1_000_000; 1 for (),1..$y;'
    ...
    8 <|> range(other->9)[t3] lK/1 <-- Range operator
    9 <#> gvsv[*y] s
    a <1> flop lKM
    goto b
    i <$> const[IV 1] s
    j <1> flip[t4] lK/LINENUM
    b <#> gv[*_] s
    c <{> enteriter(next->d last->g redo->d) lK/8 <-- No S
    ...

    这是在编译时展平的范围的样子:
    $ perl -MO=Concise,-exec -e'1 for (),1..1_000_000;'
    ...
    4 <$> const[AV ] s <-- Constant array
    5 <1> rv2av lKPM/1
    6 <#> gv[*_] s
    7 <{> enteriter(next->8 last->b redo->8) lK/8 <-- No S
    ...

    你可以看到 for (CONST..CONST)创建一个 enteriter带有“S”标志。上 enteriter ,“S”标志表示这是一个计数循环。
    $ perl -MO=Concise,-exec -e'1 for 1..1_000_000;'
    ...
    4 <$> const[IV 1] s
    5 <$> const[IV 1000000] s
    6 <#> gv[*_] s
    7 <{> enteriter(next->8 last->b redo->8) lKS/8 <-- S
    ...

    for (EXPR..EXPR) 相同一般来说。
    $ perl -MO=Concise,-exec -e'$y=1_000_000; 1 for 1..$y;'
    ...
    8 <$> const[IV 1] s
    9 <#> gvsv[*y] s
    a <#> gv[*_] s
    b <{> enteriter(next->c last->f redo->c) lKS/8 <-- S
    ...

    for (@a)没有压扁!
    $ perl -MO=Concise,-exec -e'1 for @a;'
    ...
    4 <#> gv[*a] s
    5 <1> rv2av[t2] sKRM/1
    6 <#> gv[*_] s
    7 <{> enteriter(next->8 last->b redo->8) lKS/8 <-- S
    ...

    再检查一遍
    $ perl -MO=Concise,-exec -e'1 for (),@a;'
    ...
    4 <#> gv[*a] s
    5 <1> rv2av[t2] lKM/1
    6 <#> gv[*_] s
    7 <{> enteriter(next->8 last->b redo->8) lK/8 <-- No S
    ...

    查找“S”标志的代码将确认所有这些。
  • pp_enteriter ( PL_op->op_flags & OPf_STACKED 检查“S”)
  • pp_iter .
  • 关于perl - 证明在循环中使用范围运算符不会占用额外的内存,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25214138/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com