对于背景(和理解(并试图避免这个问题似乎吸引的反对票)),我将解释让我解决这个问题的途径(好吧,两个月后我能回忆起的最好的)。
假设您正在对 Unicode 字符列表进行一些 shell 测试:
printf "$(printf '\\U%x ' {33..200})"
Run Code Online (Sandbox Code Playgroud)
并且有超过 100 万个 Unicode 字符,测试其中的 20.000 个似乎并不多。
还假设您将字符设置为位置参数:
set -- $(printf "$(printf '\\U%x ' {33..20000})")
Run Code Online (Sandbox Code Playgroud)
目的是将字符传递给每个函数以不同的方式处理它们。所以函数应该具有形式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 "$@"
Run Code Online (Sandbox Code Playgroud)
功能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
Run Code Online (Sandbox Code Playgroud)
结果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
Run Code Online (Sandbox Code Playgroud)
function 中没有使用参数或输入或输出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
Run Code Online (Sandbox Code Playgroud)
结果:
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 ......
Run Code Online (Sandbox Code Playgroud)
取消对其他两个测试的注释以确认没有seq或正在处理参数列表是延迟的来源。
复制自:为什么在循环中延迟?根据您的要求:
您可以将测试用例缩短为:
time bash -c 'f(){ :;};for i do f; done' {0..10000}
Run Code Online (Sandbox Code Playgroud)
它正在调用一个$@似乎触发它的函数。
我的猜测是时间花在保存$@到堆栈上并在之后恢复它。bash通过复制所有值或类似的东西,可能会非常低效。时间似乎在 o(n²) 中。
您在其他 shell 中获得相同类型的时间:
time zsh -c 'f(){ :;};for i do f "$@"; done' {0..10000}
Run Code Online (Sandbox Code Playgroud)
这就是您将参数列表传递给函数的地方,这一次 shell需要复制值(bash最终会慢 5 倍)。
(我最初认为它在 bash 5(目前处于 alpha 阶段)中更糟,但这归结为@egmont 指出的开发版本中启用了 malloc 调试;bash如果您想将自己的构建与系统之一。例如,Ubuntu 使用--without-bash-malloc)