Bash使用参数列表有性能问题吗?


11

在bash 5.0中解决

背景

对于背景(和理解(并试图避免这个问题似乎吸引人们的投票)),我将解释导致该问题的途径(嗯,两个月后我能记得的最好的)。

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

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

并且有超过一百万个Unicode字符,测试其中的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中进行测试,以发现只有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个


将测试扩展到几个外壳程序,结果是一致的,大多数外壳程序都没有问题也没有延迟(使用相同的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


3
通过meta效果保存。unix.meta.stackexchange.com/q/5021/3562
约书亚

Answers:


9

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

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

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中),但这归结于@egmont指出的在开发版本中启用了malloc调试;bash如果您想将自己的构建与系统的一个。例如,Ubuntu使用--without-bash-malloc


如何删除调试?
以撒

@isaac,我改变这样做是RELSTATUS=alphaRELSTATUS=releaseconfigure脚本。
斯特凡Chazelas

--without-bash-malloc和结果都添加了测试RELSTATUS=release结果。仍然显示对f的调用存在问题。
艾萨克

@Isaac,是的,我只是说我过去常常说bash5中的情况更糟。这并不坏,也一样糟糕。
斯特凡Chazelas

不,还不错。Bash5解决了呼叫问题,:并在呼叫方面有所改进f。查看问题中的test2计时。
以撒
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.