高负载下sed命令运行极慢如何解决?
问题描述
我有一个bash脚本,用于读取每行格式为foo=bar的配置文件,并将配置值替换到另一个文件中。脚本代码如下:
INPUT=`cat $INPUT_FILE` while read line; do PROP_NAME=`echo $line | cut -f1 -d'='` PROP_VALUE=`echo $line | cut -f2- -d'=' | sed 's/\$/\\\$/g` time INPUT="$(echo "$INPUT" | sed "s\`${PROP_NAME}\b\`${PROP_VALUE}\`g")" done <<<$(cat "$PROPERTIES_FILE") # Do more stuff with INPUT
但当机器处于高负载(数值达40以上)时,sed命令的耗时大幅增加:
real 0m0.169s user 0m0.001s sys 0m0.006s
低负载时的耗时:
real 0m0.011s user 0m0.002s sys 0m0.004s
通常0.1秒的耗时增加影响不大,但配置文件和输入文件均有数百/数千行,累计耗时超1小时。请问该如何解决?是否需要增加CPU?
示例配置文件(行首含特殊字符,用于标识输入文件中的配置引用):
$foo=bar $hello=world ^hello=goodbye
示例输入文件:
This is a story about $hello. It starts at a $foo and ends in a park. Bob said to Sally "^hello, see you soon"
预期输出结果:
This is a story about world. It starts at a bar and ends in a park. Bob said to Sally "goodbye, see you soon"
解决方案
1. 核心优化:减少进程创建次数
你的脚本最大问题是每次循环都启动多个子进程(echo、cut、sed各一次),且每次sed都要重新处理整个INPUT内容。高负载下进程调度开销被放大,这才是耗时暴涨的根源——不是CPU算力不够,是进程切换太频繁。
推荐用awk一次性完成所有替换,仅启动一个进程:
BEGIN { # 加载配置文件到关联数组 while ((getline line < ARGV[1]) > 0) { split(line, kv, "=") key = kv[1] value = substr(line, index(line, "=")+1) # 转义awk特殊字符,避免替换出错 gsub(/\\/, "\\\\", value) gsub(/"/, "\\\"", value) map[key] = value } close(ARGV[1]) ARGV[1] = "" } # 处理输入文件每一行 { line = $0 for (key in map) { # 精确匹配完整的配置键,避免部分匹配 line = gensub("\\<" key "\\>", map[key], "g", line) } print line }
调用方式:
awk -f replace.awk properties.txt input.txt > output.txt
这种方式全程在内存中处理,不管配置有多少条,只启动一次awk进程,进程调度开销几乎为0,高负载下性能不会大幅下降。
2. 原bash脚本的轻量化优化
如果必须保留bash环境,至少将多次子进程调用合并,避免循环内重复处理整个INPUT:
# 先将所有配置项生成完整的sed替换脚本 sed_script=$(awk -F= '{print "s/\\<"$1"\\>/"$2"/g"}' "$PROPERTIES_FILE") # 一次性完成所有替换 INPUT=$(sed "$sed_script" "$INPUT_FILE")
该方法将进程数从N次(N为配置行数)降到2次(awk+sed),性能提升显著。
3. 是否需要增加CPU?
不需要。从耗时统计可以看出,user时间(实际CPU计算时间)几乎没有变化,sys时间和real时间暴涨,说明大部分时间都消耗在等待进程调度、IO上下文切换上。优化脚本减少进程数,比增加CPU的效果好得多。
内容的提问来源于stack exchange,提问作者Bryan Tan
相关产品推荐
相关产品推荐

