Gau*_*ani 5 performance shell bash shell-script time
我运行了一个简单的脚本来生成一个包含 6 个字段的大型(10000000 行)csv 文件,其中使用while循环在每行/行中更改了一些字段。这台机器有全部 (32) 个 CPU 空闲,大量 RAM (~31 Gb) 也是空闲的。
我用命令为脚本计时
/usr/bin/time -v bash script.01.sh
运行了大约 2 个小时后,我得到了以下统计数据:
正在计时的命令:“bash script.01.sh”
用户时间(秒):1195.14
系统时间(秒):819.71
此作业获得的 CPU 百分比:27%
已用(挂钟)时间(h:mm:ss 或 m: ss): 2:01:10
平均共享文本大小 (kbytes): 0
平均未共享数据大小 (kbytes): 0
平均堆栈大小 (kbytes): 0
平均总大小 (kbytes): 0
最大驻留集大小 (kbytes): 4976
平均驻留集大小(千字节):0
主要(需要 I/O)页面错误:0
次要(回收帧)页面错误:3131983488
自愿上下文切换:22593141
非自愿上下文切换:10923348
交换:0
文件系统输入:0
文件系统输出:2182920
发送的
套接字消息:0 接收的套接字消息:0
传递的信号:0
页大小(字节):4096
退出状态:0
我想知道为什么我的脚本只使用了 27% 的 CPU?磁盘 IO 根本没有什么(在 vmstat 输出中看到)。那么是什么导致了限制呢?脚本中的代码?
这是脚本:
#!/usr/bin/env bash
number=1
while [[ $number -lt 10000001 ]] ; do
fname="FirstName LastName $"
lname=""
email="fname.lname.$number@domain.com"
password="1234567890"
altemail="lname.fname.$number@domain.com"
mobile="9876543210"
echo "$fname,$lname,$email,$password,$altemail,$mobile" >> /opt/list.csv
number=$(expr $number + 1)
done
Run Code Online (Sandbox Code Playgroud)
Mar*_*ick 14
通过使用strace
,我看到该行
number=$(expr $number + 1)
Run Code Online (Sandbox Code Playgroud)
导致 fork、路径搜索和 exec expr
。(我在 Ubuntu 上使用 bash 4.2.45)。文件系统、磁盘和进程开销导致 bash 只获得大约 28% 的 CPU。
当我将该行更改为仅使用 shell 内置操作时
((number = number + 1))
Run Code Online (Sandbox Code Playgroud)
bash 使用了大约 98% 的 CPU,脚本在半小时内运行。这是在单 CPU 1.5GHz 赛扬上。
该脚本不执行任何并行运行的操作,因此拥有 32 个空闲 CPU 不会有太大帮助。但是,您当然可以将其并行化,例如,将其拆分为 10 个并行运行的 100 万次迭代循环,写入 10 个不同的文件,然后使用cat
组合它们。
@Arthur2e5 添加了以下示例程序:
max=1000000 step=40000 tmp="$(mktemp -d)"
# Spawning. For loops make a bit more sense in a init-test-incr pattern.
for ((l = 0; l < max; l += step)); do (
for ((n = l + 1, end = (step + l > max ? max : step + l);
n <= end; n++)); do
# Putting all those things into the `buf` line gives you a 1.8x speedup.
fname="FirstName LastName \$"
lname=""
email="fname.lname.$n@domain.com"
password="1234567890"
altemail="lname.fname.$n@domain.com"
mobile="9876543210"
buf+="$fname,$lname,$email,$password,$altemail,$mobile"$'\n'
done
printf '%s\n' "$buf" > "$tmp/$l" ) &
done # spawning..
wait
# Merging. The filename order from globbing will be a mess,
# since we didn't format $l to some 0-prefixed numbers.
# Let's just do the loop again.
for ((l = 0; l < max; l += step)); do
printf '%s\n' "$(<"$tmp/$l")" >> /opt/list.csv
done # merging..
rm -rf -- "$tmp" # cleanup
Run Code Online (Sandbox Code Playgroud)