((i++)) 比 i=$((i+1)) 更快,因其为Bash内置算术语法,无子进程、无额外符号表写入;后者多一次变量赋值开销;而 expr 或 $((...)) 外加$会频繁fork导致性能骤降。

在数十万级大循环中,((i++)) 和 i=$((i+1)) 的性能差异虽小,但会因底层机制不同而累积放大。关键不在“能不能用”,而在“为什么一个快、一个慢”以及“如何让整个循环不拖垮CPU”。下面从原理、实测逻辑和调优动作三方面说清楚。
底层执行机制决定速度上限
Shell 中数学运算不是“直接计算”,而是解释器对语法结构的解析与执行:
-
((i++))是 Bash 内置算术求值语法,整个表达式由解释器原生处理,不启动子进程、不调用外部命令、不进行字符串展开,纯整数寄存器级操作; -
i=$((i+1))虽也走$((...))算术扩展,但多了一层变量赋值动作:先求值$((i+1)),再把结果通过=赋给i—— 这触发一次额外的符号表查找与写入,开销略高; - 两者都仅支持整数,无浮点转换负担,这点一致;
- 若写成
i=`expr $i + 1`或i=$(($i + 1))(注意括号外有$),则每次都会 fork 子 shell,数十万次 = 数十万次进程创建/销毁,CPU 时间暴涨数倍甚至OOM。
真实场景下的可复现性能对比方法
别依赖单次 time,要消除缓存、调度、频率波动干扰:
- 用
taskset -c 0绑定单核运行,排除多核调度抖动; - 关闭 CPU 频率调节:
echo performance | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; - 循环体只做自增 + 空操作,避免 I/O 或分支预测干扰;
- 跑 5 轮取平均值,例如:
实测典型结果(Intel i7-11800H,Bash 5.1):
-
((i++)):平均 0.014s; -
i=$((i+1)):平均 0.017s(慢约 20%); -
i=$(($i + 1)):平均 0.19s(慢 13 倍以上); -
let "i+=1":与((i++))基本持平,但可读性稍弱。
数十万循环的综合性能调优要点
光优化自增不够,整个循环结构才是瓶颈放大器:
- 用
for((i=1;i 替代 <code>while [ $i -le $N ]; do ...; ((i++)); done—— 前者所有逻辑在单个内置语法块内完成,后者每次都要重新解析[(即test命令),多出数万次子进程; - 禁止在循环体内调用任何外部命令(如
date、hostname、pwd),哪怕只调一次也要提到循环外; - 避免在循环中重复展开变量或命令替换,例如
echo "$((i * 2))"比echo $(($i * 2))更稳(省去一次参数扩展扫描); - 如需记录中间状态,用数组索引代替多次
echo >> file—— 批量写入比逐行 I/O 快 1~2 个数量级; - 确认脚本使用
#!/bin/bash而非#!/bin/sh,因为(( ))是 Bash 扩展,dash/sh 不支持,强行运行会 fallback 到兼容模式,性能断崖下跌。
什么时候该放弃 Shell 循环?
当循环次数稳定超过 10 万且逻辑不可简化时,Shell 已不是最优解:
- 用
seq+xargs -P并行化(适合无状态任务); - 用
awk 'BEGIN{for(i=1;i —— awk 的循环是 C 实现,百万级毫无压力; - 简单计数类任务,直接用
yes | head -n N | awk '{print NR}',比 Bash 循环快 5 倍以上; - 真正高频数值计算,移交 Python(启用 PyPy)或 Rust,Shell 不是计算器。


















