语境
我有一个包含数千个 zip 文件的目录,这些文件都以表格形式注明日期YYYYMMDD_hhmmss.zip
,每个文件大约 300K。每个 zip 文件内大约有 400 个 xml 文件,每个文件大约 3K。
问题
我需要能够在 zip 文件的日期范围内搜索并找到给定的字符串。
当前(尽管平庸)的解决方案
我有以下一行
find /home/mydir/ -type f | sort | \
awk "/xml_20140207_000016.zip/,/xml_20140207_235938.zip/" | \
xargs -n 1 -P 10 zipgrep "my search string"
重点是
- 列出我的千个文件目录中的所有文件
- 对此文件列表进行排序
- 根据给定日期检索一系列文件(此
awk
命令仅打印第一个匹配字符串之后的行和第二个匹配字符串之前的行) - 将对应于单个文件的每一行结果传递给
zipgrep
问题
即使在 24 核机器上有 10 个进程,这一单行代码的运行速度也非常慢。我相信由于zipgrep
命令的原因它很慢,但我不够聪明,不知道如何改进它。我不知道我是否应该这样做,但我有点尴尬,因为一位同事编写了一个运行速度比这个脚本更快的java工具。如果可能的话我想扭转这一局面。那么,有谁知道如何在这种情况下使该命令更快?或者改进其中的任何部分?
答案1
有一个部分你可以轻松改进,但它并不是最慢的部分。
find /home/mydir/ -type f | sort | \ awk "/xml_20140207_000016.zip/,/xml_20140207_235938.zip/"
这有点浪费,因为它首先列出所有文件,然后对文件名进行排序并提取感兴趣的文件。命令find
必须运行完毕后才能开始排序。
首先只列出感兴趣的文件,或者至少列出尽可能小的超集,会更快。如果您需要对名称进行更细粒度的过滤器find
,请通过管道输入 awk,但不要排序:awk 和其他逐行过滤器可以逐行处理行,但排序需要完整的输入。
find /home/mydir/ -name 'xml_20140207_??????.zip' -type f | \
awk 'match($0, /_[0-9]*.zip$/) &&
(time = substr($0, RSTART+1, RLENGTH-5)) &&
time >= 16 && time <= 235938' |
xargs -n 1 -P 10 zipgrep "my search string"
最明显次优的部分是 zipgrep。由于 shell 编程的限制,这里没有简单的方法来提高性能。 zipgrep 脚本的操作方式是列出存档中的文件名,并grep
一一调用每个文件的内容。这意味着 zip 存档中的每个文件都会被一次又一次地解析。 Java 程序(或 Perl、Python、Ruby 等)可以通过仅处理文件一次来避免这种情况。
如果您想坚持使用 shell 编程,可以尝试挂载每个 zip 而不是使用 zipgrep。
… | xargs -n1 -P2 sh -c '
mkdir "mnt$$-$1";
fuse-zip "$1" "mnt$$-$1";
grep -R "$0" "mnt$$-$1"
fusermount -u "mnt$$-$1"
' "my search string"
请注意,并行性不会给您带来太大帮助:大多数设置的限制因素是磁盘 I/O 带宽,而不是 CPU 时间。
我没有对任何东西进行基准测试,但我认为最大的改进地方是使用更强大的语言中的 zipgrep 实现。
答案2
一些快速的想法;
- 如果所有文件都在一个目录中,您可以删除
find
- 您的文件名约定按日期排序,因此您也不需要该
sort
位 解决了这两部分之后,如果日期范围已知,您可以使用简单的文件名 glob 而不是 awk。例如(假设您的 shell 是
bash
):一天的所有文件
echo xml_20140207_*.zip | xargs -n 1 -P 10 zipgrep "my search string"
2014 年 2 月 7 日或 2 月 10 日 15:00 至 18:00 之间创建的文件:
echo xml_201402{07,10}_1{5..7}*.zip | xargs -n 1 -P 10 zipgrep "my search string"
答案3
目前还不清楚你的瓶颈在哪里。让我们假设它是在读取文件。根据您的存储系统,在处理之前读取整个文件会更快。对于对文件进行几次查找的情况尤其如此zipgrep
:如果文件未完全位于内存中,您将等待磁盘进行查找。
find ... | parallel -j1 'cat {} >/dev/null; echo {}' | parallel zipgrep "my search string"
上面的cat
代码一次将一个文件放入内存缓存中,然后zipgrep
每个 CPU 运行一个文件,然后从内存缓存中读取。
我使用过 RAID 系统,并行读取 10 个文件比一次读取 1 个文件或并行读取 30 个文件的速度提高了 6 倍。如果我必须在该 RAID 系统上运行上述内容,我会调整-j1
为-j10
.
通过使用 GNU Parallel 而不是xargs
你可以保护自己免受输出混合的影响(请参阅http://www.gnu.org/software/parallel/man.html#DIFFERENCES-BETWEEN-xargs-AND-GNU-Parallel)。