合并数百万个文件的最快方法

问题描述

在远程计算机的一个目录中存储了8100万个文件(!)。所有文件都以“ .paintedHaploDiversity”结尾。我想将这些文件合并到父目录中名为allOutputs_3.5的文件中。更具体地说,每个文件包含两行或三行。第一行是我可以忽略的标头。在其余的一两行中,其中一行在第四列中的值为2。对于每个文件,我要复制第二行中有2的整行,并在其中添加文件名(不包括扩展名“ .paintedHaploDiversity”)。我将此文件名称为“ simID”。

有关信息,远程计算机可在MAC OS X 10.11.6(15G22010)上运行。这是一个简单的destkop。因此,不涉及任何网络(在我的ssh命令之外可以访问远程计算机)。

我第一次尝试

for f in *;
do
   simID=${f%.paintedHaploDiversity}
   awk -v simID=${simID} 'NR>1{if ($4==2) {printf simID"\t"; print}}' $f >> ../allOutputs_3.5
done

但是非常慢。我估计需要几个月甚至几年的时间!然后,我尝试了

awk 'FNR==1{simID=substr(FILENAME,1,length(FILENAME)-22)}FNR>1{if ($4==2) {printf simID"\t"; print}}' * >> ../allOutputs

,但似乎没有更快。就像速度测试一样,我也考虑过

find . -exec cat '{}' ';' > out

但是它又很慢。考虑到问题可能来自正则表达式扩展*,我试图通过两个C样式循环来复制每个文件的名称,从而遍历每个文件。

for ((bigID=1; bigID <= 9 ;++bigID)); do
   for ((rep=1; rep <= 9000000 ;++rep)); do
      awk -v simID=3.5.${bigID}_${rep} 'NR>1{if ($4==2) {printf simID"\t"; print}}' 3.5.${bigID}_${rep}.paintedHaploDiversity >> ../allOutputs_3.5
   done
done

该过程现在要快很多,但仍需要数月才能运行!最后,我想,我还是最好稍后删除第二列不等于2的行(也许使用sed命令)并这样做

for ((bigID=1; bigID <= 6 ;++bigID)); do
   for ((r=1; r <= 9000000 ;++r)); do
      printf "3.5_${bigID}_${r}\t"  >> ../allOutputs_3.5
      tail -n +2 3.5_${bigID}_${r}.paintedHaploDiversity >> ../allOutputs_3.5
   done
done

现在,该过程大约需要两个星期。那开始是合理的。我仍然想知道是什么导致该过程如此缓慢以及是否可以改进。

我认为瓶颈可能是磁盘IO。还是需要大量CPU时间的文件系统?由于同一目录中有太多文件,并且每次循环迭代时都需要搜索文件的二进制树,因此该过程是否如此缓慢?如何改善?我应该尝试用C ++编写过程吗?

如果这有帮助,请在运行最后一条命令(使用top -o MEMprintf的命令)时输出tail

Processes: 254 total,3 running,12 stuck,239 sleeping,1721 threads                            03:12:40
Load Avg: 2.04,1.79,1.60  CPU usage: 0.84% user,4.33% sys,94.81% idle
SharedLibs: 85M resident,11M data,10M linkedit.
MemRegions: 42324 total,4006M resident,63M private,230M shared.
PhysMem: 14G used (2286M wired),10G unused.
VM: 753G vsize,535M framework vsize,1206153(0) swapins,2115303(0) swapouts.
Networks: packets: 413664671/284G in,126210468/104G out.
Disks: 1539349069/12T read,1401722156/7876G written.

PID    COMMAND      %CPU TIME     #TH    #WQ  #PORTS MEM    PURG  CMPRS  PGRP  PPID  STATE
0      kernel_task  42.1 1716 hrs 167/25 0    2-     1968M  0B    0B     0     0     running
366    SystemUIServ 0.4  24:42:03 5      2    345    1055M  0B    10M    366   1     sleeping
472    softwareupda 0.0  12:46:11 5      0    3760   340M   0B    18M    472   1     sleeping
54242  Sublime Text 0.0  03:55:44 12     0    237    233M   0B    68K    54242 1     sleeping
63     powerd       0.0  44:07:21 2      0    95     204M   0B    8932K  63    1     sleeping
34951  Finder       0.1  04:11:06 9      2    1665   166M   0B    68M    34951 1     sleeping
197    WindowServer 0.0  40:02:58 3      0    453    142M   0B    63M    197   1     sleeping
13248  Terminal     0.0  84:19.45 5      0    388    114M   0B    113M   13248 1     sleeping
29465  X11.bin      0.0  89:38.70 9      0    229    104M   0B    16M    29464 29464 sleeping
12372  system_insta 0.0  00:31.61 2      0    75     78M    0B    9996K  12372 1     sleeping
1588   sysmond      0.0  02:34:04 2      1    23     62M    0B    4536K  1588  1     sleeping
54245  plugin_host  0.0  00:03.88 5      0    56     51M    0B    0B     54242 54242 sleeping
554    spindump     0.0  00:36.51 2      1    164    44M    0B    33M    554   1     sleeping
20024  com.apple.GS 0.0  00:01.43 3      2    24     43M    0B    2200K  20024 1     sleeping
475    suhelperd    0.0  00:19.84 2      0    55     42M    0B    28M    475   1     sleeping
418    installd     0.0  01:21.89 2      0    69     40M    0B    12M    418   1     sleeping
57     fseventsd    0.1  13:03:20 10     0    241    39M    0B    2904K  57    1     sleeping
364    Dock         0.0  08:48.83 3      0    283    38M    0B    27M    364   1     sleeping
201    sandboxd     0.0  18:55.44 2      1    38     38M    0B    10M    201   1     sleeping
103    loginwindow  0.0  04:26.65 2      0    377    35M    0B    3400K  103   1     sleeping
897    systemstatsd 0.0  65:30.17 2      1    43     34M    0B    4928K  897   1     sleeping
367    fontd        0.0  11:35.30 2      0    77     32M    0B    5920K  367   1     sleeping
396    ScopedBookma 0.0  01:00.46 3      2    46     32M    0B    28M    396   1     sleeping
22752  cfbackd      0.4  32:18.73 9      1    84     30M    0B    0B     22752 1     sleeping
39760  Preview      0.0  00:03.75 3      0    209    29M    0B    0B     39760 1     sleeping
53     syslogd      0.0  05:33:59 4      3    186-   29M-   0B    1668K  53    1     sleeping
533    SmartDaemon  0.0  27:07.67 10     7    175    28M    128K  5192K  533   1     stuck   
388    iconservices 0.0  00:08.85 2      1    66     27M    0B    157M   388   1     sleeping
7268   diskmanageme 0.0  00:40.14 888    0    8899   27M    0B    7352K  7268  1     sleeping
513    Notification 0.0  00:46.42 3      0    245    26M    0B    9852K  513   1     sleeping
83     opendirector 0.0  19:22:12 6      5    8827   26M    0B    2444K  83    1     sleeping
557    AppleSpell   0.0  03:12.61 2      0    57     26M    0B    10M    557   1     sleeping
422    com.apple.ge 0.0  01:50.41 5      0    83     25M    0B    1680K  422   1     sleeping
397    storeaccount 0.0  00:48.41 4      0    1333   21M    0B    2248K  397   1     sleeping
87     launchservic 0.0  64:26.85 3      2    306    20M    0B    5804K  87    1     sleeping
1      launchd      0.0  26:26:23 5      4    1802   20M    0B    6532K  1     0     stuck   
222    taskgated    0.0  17:59:00 3      1    43     19M    0B    4528K  222   1     sleeping
54     UserEventAge 0.0  18:19.74 3      0    32605- 18M-   0B    2968K  54    1     sleeping
4527   com.apple.sp 0.0  00:13.01 2      0    48     17M    0B    7792K  4527  1     sleeping
79     coreduetd    0.0  05:40.06 2      0    95     17M    0B    4604K  79    1     sleepin

这是iostat

的输出
      disk0           disk1           disk2       cpu     load average
KB/t tps  MB/s     KB/t tps  MB/s     KB/t tps  MB/s  us sy id   1m   5m   15m
7.19 152  1.07     8.10   0  0.00     8.22   0  0.00  15 50 35  1.68 1.74 1.59

示例:

考虑以下文件

文件_0:

first second third fourth fifth
bbb a a 2 r

文件_1:

first second third fourth fifth
f o o 2 o

文件_2:

first second third fourth fifth
f r e 1 e
x xxx x 2 x

file_3:

first second third fourth fifth
a a a 2 a

预期输出是

file_0 bbb a a 2 r
file_1 f o o 2 o
file_2 x xxx x 2 x
file_3 a a a 2 a

解决方法

您可能可以应付对程序grepsed的两次调用。这应该很快。甚至比自编写的C程序还要快。

cd dir_with_all_the_files
grep -rE '^([^ ]+ +){3}2 ' . | 
sed -En 's/^\.\/(.*)\.paintedHaploDiversity:/\1 /p' > ../allOutputs_3.5

所做的假设:

  • 要搜索的列的标题也不是2
  • 该目录不包含子目录。
    该命令可能仍会产生正确的结果,但必须运行不必要的时间。
  • 文件名不包含:或换行符。
  • 您的grep实现支持非Posix -r选项(通常是这种情况)。

如果您的grep实现支持进一步的改进:

  • 添加-m1可以加快搜索速度。
  • 尝试grep -P(在Mac OS中通常不支持)或pcregrep。 PCRE有时更快。使用PCRE,您还可以尝试其他正则表达式'^(.*? ){3}2 '
  • --exclude-dir \*(请注意用引号*)排除了子目录,因此即使没有上述假设,您也可以使用该命令。

如果您希望输出按文件名排序(如迭代*.paintedHaploDiversity时所得到的那样),请随后运行sort -t ' ' -k 1,1 -o allOutputs_3.5{,}

您最好将export LC_ALL=C设置为加快grepsort甚至是sed的速度。

,

困难的问题。可能把自己画在那儿的角落里...

如果甚至find命令花费的时间太长,除了打开,读取和关闭每个文件之外什么也不做,那么可能的瓶颈是HDD上的查找时间。这通常约为10毫秒(source),因此对于8100万个文件,假设每个文件一次查找,则需要将近10天的时间。由于文件系统(目录访问等)的缘故,它的搜索次数可能会更多,但是如果本地性良好,则每次搜索的时间可能也会较短。

如果您有足够的时间等待一次,我建议将所有这些文件压缩成一个文件。这将花费很多时间,但是之后您可以更快地处理数据集。

如果无法压缩(或以其他方式复制或访问)每个文件,则解决方案可能是拍摄整个文件系统的映像(快照)并将其复制到更快的驱动器上。 SSD的寻道时间约为0.1毫秒(source),因此使用SSD可以在两个多小时内完成工作。

更严格的方法是编写直接在原始磁盘字节上运行的代码,实现文件系统的必要部分,并使用较大的内存缓冲区来避免磁盘寻道。根据文件在磁盘上的分散方式,这可能会大大提高速度,但是对它进行编程当然是一项不小的努力。

,

问题(除了处理数GB数据的明显I / O负载之外)是,启动一个或多个进程8100万次需要很长时间。甚至创建命令行或将文件范围扩展到300MB(for f in *...)都可能需要大量时间,或者超出系统和程序的规格。

一种解决方案是编写一个C程序来打开文件并处理它们,或将它们的内容通过管道传递给其他程序。但这可能需要花费几天的时间进行编程和调试,并且您的实习生可能正在休假。但是Unix工具箱中已经有一些程序可以满足您的需要,但文件名会丢失。我们假定所有文件都在一个名为bla的目录中。

使用tar创建包含文件内容的流,如下所示:

tar cf - bla | tar -xOf -

这会将文件的串联内容写成标准输出,默认情况下是控制台。 tar和grep都只能启动一次。第一个tar查找目录中的所有文件,并创建一个归档文件(某种结构化的串联),然后将其写入stdout。由于-O,第二个tar抓取了该存档,提取了文件并将其写入stdout,而不是在文件系统中创建文件。

之后,开始处理:

tar cf - bla | tar -xOf - | grep '^whatever is before the 2 \<2\>' > out.txt

如果文件名的存在是一个硬性要求,您也许可以重复处理链,但是让第二个tar发出文件名(-t选项),并将其通过管道传递到Shell脚本,该脚本从中读取一行out.txt和tar输出,将两者合并并将合并的行写入新文件。

,

如果目前printf/tail尝试被认为是最快的(2周?仅基于OP的评论),我想消除8100万个printf/tail命令对,并减少对awk/substr(FILENAME)调用适用于通配符集,该调用一次将处理分为大约10K个文件,例如:

for bigID in {1..6}
do
    # poll first 99 files (r=1..99) + 9 millionth file

    awk 'FNR==1{simID=substr(FILENAME,1,length(FILENAME)-22)}FNR>1{if ($4==2) {printf simID"\t"; print}}' 3.5_${bigID}_{1..99}.paintedHaploDiversity 3.5_${bigID}_9000000.paintedHaploDiversity >> ../allOutputs

    # break rest of files into ~10K chunks based on first 3 digits of suffix

    for r in {100..899}      # break 9000000 into ~10K chunks
    do
        awk 'FNR==1{simID=substr(FILENAME,length(FILENAME)-22)}FNR>1{if ($4==2) {printf simID"\t"; print}}' 3.5_${bigID}_${r}*.paintedHaploDiversity >> ../allOutputs
    done
done

注意:我仅选择10K作为假设,awk捕获更大的文件ID会对性能产生一定的影响;进行这种大小的测试可能会发现awk可以(迅速)处理的文件数量上有个不错的选择


此外,iostat显示3个磁盘。如果这些是3x物理上分开的磁盘,并且作为单独的磁盘连接(即,不是RAID配置的一部分),则请确保目标文件(allOutputs_3.5)与源文件位于不同的磁盘上。这样可以减少read-> write-> read-> write抖动(HDD较多,SSD较少)。

注意 :(显然)这假设其他磁盘上有空间可以容纳目标文件。

我可能想使用前面提到的每个编码尝试,使用一小部分文件(例如110K)来测试这个想法(从磁盘1读取,写入磁盘2)。 (相对)在时间上有很大的差异(因此将读/写颠簸指向一个瓶颈)。

,

任何带有bash循环的解决方案(如果您调用一百万或一个以上的进程数百万次)将非常慢。在Linux上,对我来说awk '{...}' * > output的尝试也导致了:bash: /usr/bin/awk: Argument list too long


使用findxargs

find 是您必须使用的,不与-exec 一起使用,因为这样您将再次为每个文件参数调用百万个进程,但是使用xargs ,这样您就可以将大量参数传递给一个进程。您也可以使用xargs -n批量完成该工作。通常,可能会遇到操作系统,bash参数等的任何限制,但我尚未测试大量的数据。


我在一个非常旧的盒子上执行了下面的解决方案,它比有问题的桌面要慢,并且样本800,000个文件(占问题总数的1%)花了3分钟。

find . -type f -printf "%f\n" |\
xargs awk '$4==2{ print(substr(FILENAME,length(FILENAME)-22),$0) }' >> output.txt

首先,您必须在执行过程中避免交换使用,否则它将急剧下降,其次,您可能会遇到任何限制,如上所述。因此,它可能需要分批完成,例如,您运行一次find并将结果保存到文件,将文件拆分为批处理(例如,每个文件名1M),然后将每个块的xargs打包到awk。>


如果没有find,则使用循环创建文件名:再次使用xargs

我看到您可以按照标准模式在bash循环中创建文件名,这可能比find更快,但是我相信这并不是瓶颈。同样,您不应为每个参数执行一个命令,而应通过awk将此文件提供给xargs

例如,使用循环创建文件名并将其保存到文件中。

for (( i=1;i<=9;i++ )); do
   for (( j=1;j<=9000000;j++ )); do
      printf "file_%s_%s\n" "$i" "$j" >> filenames.txt
   done
done

并将它们一次喂入awk

cat filenames.txt | xargs awk '{...}'

或分批,例如1M

split -l 1000000 -d filenames.txt chunk
for f in chunk*; do cat "$f" | xargs awk '{...}' ; done

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...