BASH/KSH中如何用parallel在while循环并行处理大文件?
问题根因
原有脚本耗时超1小时的核心原因不是串行执行,是每处理1行就要fork至少3个独立进程(echo、awk、egrep),500万行累计1500万次进程创建/销毁开销占总耗时99%以上。如果不解决单行长时间多进程调用的问题,哪怕强行并行最多只能把耗时压缩到10分钟级,达不到最优性能。
最优落地方案(单进程,无需并行,500万行处理耗时<10秒)
直接用RHEL默认自带的awk实现,一次性加载参考文件到内存,全程单进程无额外fork开销,处理逻辑完全匹配预期输出:
awk ' # 先加载参考文件data.txt,构建匹配键值映射 NR==FNR { split($4, d_col, ".") # 三类匹配键对应data文件的目标日期(第6列) date_map[$1] = $6 date_map[d_col[1]] = $6 date_map[$6] = $6 next } # 逐行处理input.txt { split($4, i_col, ".") # 按原逻辑优先级匹配:第一列 > 第四列前缀 > 第六列 if ($1 in date_map) res = date_map[$1] else if (i_col[1] in date_map) res = date_map[i_col[1]] else if ($6 in date_map) res = date_map[$6] # 按格式输出 print $0, res } ' data.txt input.txt > result.txt
执行以上命令得到的result.txt和给出的预期输出完全一致。
GNU Parallel并行方案(适用于参考文件过大无法全量加载到内存的场景)
如果因为特殊场景必须用并行处理,按以下步骤执行,可获得接近CPU核心数的线性加速比:
- 先安装依赖(RHEL默认源如果没有parallel,先启用epel源即可):
yum install -y parallel - 执行并行处理脚本:
# 创建临时切分目录 mkdir -p ./tmp_task # 按10万行每块切分大文件,避免单块过小导致调度开销过高 split -l 100000 input.txt ./tmp_task/block_ # 自动匹配CPU核心数作为并发度,并行处理所有切分块 find ./tmp_task -name "block_*" | parallel -j $(nproc) ' awk " NR==FNR { split(\$4, d_col, \".\") date_map[\$1] = \$6 date_map[d_col[1]] = \$6 date_map[\$6] = \$6 next } { split(\$4, i_col, \".\") if (\$1 in date_map) res = date_map[\$1] else if (i_col[1] in date_map) res = date_map[i_col[1]] else if (\$6 in date_map) res = date_map[\$6] print \$0, res } " data.txt {} > {}.out ' # 合并所有结果 cat ./tmp_task/*.out > final_result.txt # 清理临时文件 rm -rf ./tmp_task
注意事项
- 优先使用单进程awk方案,没有切分、调度、合并的额外开销,性能远高于并行方案
- 原shell脚本存在逻辑bug:egrep的匹配规则用单引号包裹,内部的
$1/$6不会被shell替换为对应字段值,实际匹配的是字面量$1字符串,上述方案已经按预期输出规则修正了匹配逻辑 - 并行方案的切分块大小建议设置为5万-20万行,块太小会导致进程调度开销占比过高,块太大会导致单块处理时间长,无法充分利用多核性能
内容的提问来源于stack exchange,提问作者ayrton_senna
相关产品推荐
相关产品推荐

