5

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

有关信息,远程计算机在 MAC OS X 10.11.6 (15G22010) 上运行。这是一个简单的桌面。因此不涉及网络(在我的 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

但它又很慢。考虑到问题可能来自 regex 扩展*,我尝试通过两个 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 MEM那么最后一个命令(使用printfand的命令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

文件_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
4

5 回答 5

4

您可能可以应付对程序的两次单独调用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,您还可以尝试替代 regex '^(.*? ){3}2 '
  • --exclude-dir \*(请注意*引用)不包括子目录,因此即使没有上述假设,您也可以使用该命令。

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

你不妨设置export LC_ALL=C加速grep,,sort甚至可能sed

于 2020-08-20T11:05:05.587 回答
1

难题。可能把自己画到了那里的角落里……

如果find命令花费的时间太长,除了打开、读取和关闭每个文件之外什么都不做,那么可能的瓶颈是硬盘上的寻道时间。这通常约为 10 毫秒(来源),因此对于 8100 万个文件,假设每个文件进行一次搜索,您大约需要 10 天才能查看。由于文件系统(目录访问等),它可能会进行更多的搜索,但如果局部性好,每次搜索也可能会更短。

如果您可以等这么久我建议您将所有这些文件压缩到一个文件中。这将花费大量时间,但之后您可以更快地处理数据集。

如果压缩(或以其他方式复制或访问)每个单独的文件是不可能的,那么解决方案可能是获取整个文件系统的图像(快照)并将其复制到更快的驱动器上。SSD 的寻道时间约为 0.1 毫秒(来源),因此使用 SSD 可以在两个多小时内完成。

更核心的方法是编写直接在原始磁盘字节上运行的代码,实现文件系统的必要部分并使用大型内存缓冲区来避免磁盘寻道。根据文件在磁盘上的分散方式,这可能会给您带来很大的加速,但当然,对此进行编程是一项不平凡的工作。

于 2020-08-20T10:52:22.050 回答
0

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

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

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

tar cf - bla | tar -xOf -

这会将文件的连接内容写入标准输出,默认情况下是控制台。tars 和 grep 都只启动一次。第一个 tar 找到目录中的所有文件并创建一个存档(这是某种结构化的连接),并将其写入标准输出;第二个 tar 抓取该存档,提取文件并将它们写入标准输出,而不是在文件系统中创建文件,这要归功于-O.

之后,开始处理:

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

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

于 2020-08-20T11:06:07.100 回答
0

任何带有bash循环的解决方案,在其中你调用数百万次一个或多个进程,都会非常慢。我在 linux 上的尝试也awk '{...}' * > output导致:bash: /usr/bin/awk: Argument list too long.


find和_xargs

find是你必须使用的,不是-exec因为这样你将再次调用每个文件参数的百万个进程,而是使用xargs,这样你可以将大量参数传递给一个进程。您也可以使用xargs -n. 一般来说,您的操作系统、bash 参数等有可能受到任何限制,但我还没有测试过大量数据。


我在一个非常旧的盒子上执行了下面的解决方案,比相关桌面慢,并且 800K 文件的样本(占所讨论文件总数的 1%)花了 3 分钟。

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

首先,您必须在执行期间避免使用交换,否则它会显着减慢,其次,您可能会达到任何限制,如上所述。所以它可能需要分批完成,例如您运行find一次并将结果保存到文件中,将文件拆分为批次(例如,每个 1M 文件名)并将每个块 xargs 到awk.


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

我看到您可以在bash循环中创建文件名,因为它们遵循标准模式,这可能更快find,但我相信这不是瓶颈。同样,您不应为每个参数执行一个命令,而是将此文件提供给awkthrough 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
于 2020-08-21T00:03:26.087 回答
0

如果此时该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, 1, 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正在显示 3x 磁盘。如果这些是 3x 物理上独立的磁盘并且它们作为独立的磁盘连接(即,不是 RAID 配置的一部分),那么请确保目标文件 ( allOutputs_3.5) 与源文件位于不同的磁盘上。这应该减少读取->写入->读取->写入抖动(HDD 更是如此,SSD 更少)。

注意:这(显然)假设其他磁盘上有空间来保存目标文件。

我可能想用一小部分文件(例如,110K)测试这个想法(从磁盘#1读取,写入磁盘#2),使用前面提到的每个编码尝试,看看是否有一个(相对)时间上的巨大差异(因此指出读/写抖动是一个瓶颈)。

于 2020-08-20T12:52:36.703 回答