问题描述
在远程计算机的一个目录中存储了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 MEM
和printf
的命令)时输出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
解决方法
您可能可以应付对程序grep
和sed
的两次调用。这应该很快。甚至比自编写的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
设置为加快grep
,sort
甚至是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
。
使用find
和xargs
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