VAR=`cat file` 然后重复 echo "$VAR" 比重复 cat file 慢。为什么?

VAR=`cat file` 然后重复 echo "$VAR" 比重复 cat file 慢。为什么?

中大约有 10,000 个文件files/和 10,000 行metadata.csv。其中metadata.csv包含有关文件的信息。我有一个 shell 脚本,它打印有关每个文件的信息,然后打印文件的内容:

#!/bin/sh
for FILE in `find files/ -type f`
do
    ID=`echo $FILE | sed 's/some/thing/'`
    cat metadata.csv | awk -v ORS="" -v id=$ID -F "\t" '$1==id {
        print "id=\""id"\" Some=\""$2"\" Thing=\""$5"\" "}'
    cat $FILE
done

metadata.csv我想我可以通过将内容分配给变量来加快速度METADATA。我认为它不会每次都从磁盘读取文件,而是将其存储在内存中:

#!/bin/sh
METADATA=`cat metadata.csv`
for FILE in `find files/ -type f`
do
    ID=`echo $FILE | sed 's/some/thing/'`
    echo "$METADATA" | awk -v ORS="" -v id=$ID -F "\t" '$1==id {
        print "id=\""id"\" Some=\""$2"\" Thing=\""$5"\" "}'
    cat $FILE
done

但第二个并不快。第一个运行约 1 分钟,第二个运行超过 2 分钟。

它是如何工作的以及为什么第二个脚本更慢而不是更快?

编辑:在我的系统上 /bin/sh -> dash

答案1

您没有为其他人提供足够的信息来重现您的基准。我自己做了一个,发现echo使用 dash 和 ksh 的方法稍微快一些,使用 mksh 的方法也差不多。即使存在差异,比例也远小于 1:2。显然,这取决于很多东西,包括 shell、内核、实用程序的实现以及数据文件的内容。

在这两种方法之间,没有明显的赢家。从磁盘读取实际上不需要任何成本,因为文件将位于缓存中。调用cat具有分叉外部进程的开销,而echoshell 是 bultin。如果你使用的sh是bash,echo即使输出进入管道,它的内置函数也会一次一行地打印其参数,这可能是速度缓慢的原因之一。 Dash 和 ksh 不会这样做;通常它们比 bash 具有更好的性能。

您可以在脚本中进行许多优化。

  • 该方法的一个明显优化cat是使用重定向 ( <metadata.csv awk …),或metadata.csv作为参数传递给 awk。在我的测试中,重定向比 稍快echo,并且重定向和 之间没有可测量的差异awk … metadata.csv

  • 当您使用不带引号的变量扩展时,除了如果值包含某些字符,则会严重失败,它给 shell 带来了额外的工作,因为它必须进行分割和通配。始终在变量替换周围使用双引号,除非您知道为什么需要省略它们。

  • 同样,您正在解析 的输出find,这会因某些文件名而阻塞,并且需要额外的工作。规范的解决方案是使用find -exec;但这可能会更快,也可能不会更快,因为这还需要做额外的工作来启动 shell 来处理文件。
  • 我认为你的 awk 脚本是从真实的脚本中简化的。使用您显示的脚本,假设 CSV 文件的第一列仅包含正则表达式中不特殊的字符,您可以尝试使用 sed 代替;它会更神秘,但可能会更快一点,因为更专业的工具通常更快。但并不能保证你会得到改善,更不用说是可衡量的了。
  • 当您设置 时ID,您将调用外部程序。根据您在这里所做的具体操作,这可能可以使用 shell 自己的字符串操作结构来实现:它们通常不是很快,也不是很强大,但它们不需要调用外部程序。

总而言之,结合这些本地优化,我会选择

#!/bin/ksh
find files/ -type f -exec sh -c '
  for FILE do
    ID=${FILE//some/thing}
    sed '/^$ID\t/ s/\([^\t]*\)\t\([^\t]*\)\t[^\t]*\t[^\t]*\t\([^\t]*\).*/id="\1" Some="\2" Thing="\3"/' metadata.csv
    cat "$FILE"
  done' _ {} +

但可能有更快的算法。您正在处理每个文件的整个元数据集。特别是如果每​​个文件仅匹配一行,则会产生大量不必要的比较。从文件名生成 ID 列表并将其与元数据进行比较可能会更快。未经测试的代码:

#!/bin/ksh
join -j 1 -t $'\t' -o 2.1,2.2,2.5,1.2 \
     <(find files/ -type f | sed 's!/some$!/thing\t&!' | sort) \
     <(sort metadata.csv) |
awk -F '\t' '{
    print "id =\"" $1 "\" Some=\"" $2 "\" Thing=\" $3 "\"";
    system("cat \047" $4 "\047"); # Assuming no single quotes in file names
}'

相关内容