bash 5.0 已解决

bash 5.0 已解决

bash 5.0 已解决

背景

对于背景(和理解(并试图避免这个问题似乎吸引的反对票)),我将解释让我解决这个问题的路径(好吧,两个月后我能记得的最好的)。

假设您正在对 Unicode 字符列表进行一些 shell 测试:

printf "$(printf '\\U%x ' {33..200})"

而且 Unicode 字符大约超过 100 万个,测试其中 20,000 个似乎并不算多。
还假设您将字符设置为位置参数:

set -- $(printf "$(printf '\\U%x ' {33..20000})")

目的是将字符传递给每个函数以以不同的方式处理它们。所以函数应该具有以下形式test1 "$@"或类似形式。现在我意识到这在 bash 中是多么糟糕的主意。

现在,假设需要对每个解决方案进行计时(n=1000)以找出哪个更好,在这种情况下,您将得到类似于以下的结构:

#!/bin/bash --
TIMEFORMAT='real: %R'  # '%R %U %S'

set -- $(printf "$(printf '\\U%x ' {33..20000})")
n=1000

test1(){ echo "$1"; } >/dev/null
test2(){ echo "$#"; } >/dev/null
test3(){ :; }

main1(){ time for i in $(seq $n); do test1 "$@"; done
         time for i in $(seq $n); do test2 "$@"; done
         time for i in $(seq $n); do test3 "$@"; done
       }

main1 "$@"

这些功能test#非常简单,仅供在此展示。
原始功能被逐步精简,以找出巨大延迟的原因。

上面的脚本有效,您可以运行它并浪费一些时间做很少的事情。

在简化以准确查找延迟位置的过程中(经过多次试验后将每个测试函数减少到几乎没有是极端的)我决定删除向每个测试函数传递参数以找出时间改进了多少,仅6 的因数,不多。

要亲自尝试,请删除所有"$@"in 函数main1(或制作副本)并再次测试(或两者main1和副本main2(带有main2 "$@"))进行比较。这是原始帖子(OP)中的基本结构。

但我想知道:为什么 shell 需要这么长时间才“什么也不做”?是的,只有“几秒钟”,但是,为什么呢?

这让我在其他 shell 中进行测试,发现只有 bash 有这个问题。
尝试ksh ./script(与上面相同的脚本)。

这导致了这样的描述:调用不带任何参数的函数 ( ) 会被父函数 ( )test#中的参数延迟。main#这是下面的描述,也是下面的原始帖子 (OP)。

原帖。

调用函数(在 Bash 4.4.12(1)-release 中)不执行任何操作比但f1(){ :; }慢一千倍:仅有的如果有参数定义在家长调用函数,为什么?

#!/bin/bash
TIMEFORMAT='real: %R'

f1   () { :; }

f2   () {
   echo "                     args = $#";
   printf '1 function no   args yes '; time for ((i=1;i<$n;i++)); do  :   ; done 
   printf '2 function yes  args yes '; time for ((i=1;i<$n;i++)); do  f1  ; done
   set --
   printf '3 function yes  args no  '; time for ((i=1;i<$n;i++)); do  f1  ; done
   echo
        }

main1() { set -- $(seq $m)
          f2  ""
          f2 "$@"
        }

n=1000; m=20000; main1

结果test1

                     args = 1
1 function no   args yes real:  0.013
2 function yes  args yes real:  0.024
3 function yes  args no  real:  0.020

                     args = 20000
1 function no   args yes real:  0.010
2 function yes  args yes real: 20.326
3 function yes  args no  real:  0.019

函数 中没有使用任何参数,也没有使用输入或输出f1,一千 (1000) 的延迟是意外的。1


将测试扩展到多个 shell,结果是一致的,大多数 shell 没有问题,也没有延迟(使用相同的 n 和 m):

test2(){
          for sh in dash mksh ksh zsh bash b50sh
      do
          echo "$sh" >&2
#         \time -f '\t%E' seq "$m" >/dev/null
#         \time -f '\t%E' "$sh" -c 'set -- $(seq '"$m"'); for i do :; done'
          \time -f '\t%E' "$sh" -c 'f(){ :;}; while [ "$((i+=1))" -lt '"$n"' ]; do : ; done;' $(seq $m)
          \time -f '\t%E' "$sh" -c 'f(){ :;}; while [ "$((i+=1))" -lt '"$n"' ]; do f ; done;' $(seq $m)
      done
}

test2

结果:

dash
        0:00.01
        0:00.01
mksh
        0:00.01
        0:00.02
ksh
        0:00.01
        0:00.02
zsh
        0:00.02
        0:00.04
bash
        0:10.71
        0:30.03
b55sh             # --without-bash-malloc
        0:00.04
        0:17.11
b56sh             # RELSTATUS=release
        0:00.03
        0:15.47
b50sh             # Debug enabled (RELSTATUS=alpha)
        0:04.62
        xxxxxxx    More than a day ......

取消注释其他两个测试,以确认seq参数列表或处理参数列表都不是延迟的根源。

1众所周知,通过参数传递结果会增加执行时间。谢谢@slm

答案1

复制自:为什么循环中有延迟?根据您的要求:

您可以将测试用例缩短为:

time bash -c 'f(){ :;};for i do f; done' {0..10000}

它正在调用一个函数,而$@它似乎会触发它。

我的猜测是,时间花在保存$@到堆栈上并随后恢复它。bash通过复制所有值或类似的东西可能会非常低效。时间似乎是o(n²)。

您在其他 shell 中也能获得同样的时间:

time zsh -c 'f(){ :;};for i do f "$@"; done' {0..10000}

这就是您将参数列表传递给函数的地方,这一次是 shell需要复制值(bash最终速度慢 5 倍)。

(我最初认为 bash 5(目前处于 alpha 版本)中情况更糟,但这取决于在开发版本中启用了 malloc 调试,如 @egmont 所指出的;bash如果您想将自己的构建与例如,Ubuntu 使用--without-bash-malloc)

相关内容