文件 1..64 每个都是 160 MB,存储在 RAM 磁盘中。
生成者:
seq 120 | parallel -k 'seq {}0000000 {}9999999 | fmt -30' | head -c 10G > 10G
parallel --pipepart --block -1 -a 10G -j 64 'cat > {#}'
nocat
:
#!/bin/bash
export LC_ALL=C
sort -m \
<(sort -m \
<(sort -m \
<(sort -m \
<(sort -m \
<(sort -m \
<((rm 1; sort ) < 1) \
<((rm 2; sort ) < 2) ) \
<(sort -m \
<((rm 3; sort ) < 3) \
<((rm 4; sort ) < 4) ) ) \
<(sort -m \
<(sort -m \
<((rm 5; sort ) < 5) \
<((rm 6; sort ) < 6) ) \
<(sort -m \
<((rm 7; sort ) < 7) \
<((rm 8; sort ) < 8) ) ) ) \
<(sort -m \
<(sort -m \
<(sort -m \
<((rm 9; sort ) < 9) \
<((rm 10; sort ) < 10) ) \
<(sort -m \
<((rm 11; sort ) < 11) \
<((rm 12; sort ) < 12) ) ) \
<(sort -m \
<(sort -m \
<((rm 13; sort ) < 13) \
<((rm 14; sort ) < 14) ) \
<(sort -m \
<((rm 15; sort ) < 15) \
<((rm 16; sort ) < 16) ) ) ) ) \
<(sort -m \
<(sort -m \
<(sort -m \
<(sort -m \
<((rm 17; sort ) < 17) \
<((rm 18; sort ) < 18) ) \
<(sort -m \
<((rm 19; sort ) < 19) \
<((rm 20; sort ) < 20) ) ) \
<(sort -m \
<(sort -m \
<((rm 21; sort ) < 21) \
<((rm 22; sort ) < 22) ) \
<(sort -m \
<((rm 23; sort ) < 23) \
<((rm 24; sort ) < 24) ) ) ) \
<(sort -m \
<(sort -m \
<(sort -m \
<((rm 25; sort ) < 25) \
<((rm 26; sort ) < 26) ) \
<(sort -m \
<((rm 27; sort ) < 27) \
<((rm 28; sort ) < 28) ) ) \
<(sort -m \
<(sort -m \
<((rm 29; sort ) < 29) \
<((rm 30; sort ) < 30) ) \
<(sort -m \
<((rm 31; sort ) < 31) \
<((rm 32; sort ) < 32) ) ) ) ) ) \
<(sort -m \
<(sort -m \
<(sort -m \
<(sort -m \
<(sort -m \
<((rm 33; sort ) < 33) \
<((rm 34; sort ) < 34) ) \
<(sort -m \
<((rm 35; sort ) < 35) \
<((rm 36; sort ) < 36) ) ) \
<(sort -m \
<(sort -m \
<((rm 37; sort ) < 37) \
<((rm 38; sort ) < 38) ) \
<(sort -m \
<((rm 39; sort ) < 39) \
<((rm 40; sort ) < 40) ) ) ) \
<(sort -m \
<(sort -m \
<(sort -m \
<((rm 41; sort ) < 41) \
<((rm 42; sort ) < 42) ) \
<(sort -m \
<((rm 43; sort ) < 43) \
<((rm 44; sort ) < 44) ) ) \
<(sort -m \
<(sort -m \
<((rm 45; sort ) < 45) \
<((rm 46; sort ) < 46) ) \
<(sort -m \
<((rm 47; sort ) < 47) \
<((rm 48; sort ) < 48) ) ) ) ) \
<(sort -m \
<(sort -m \
<(sort -m \
<(sort -m \
<((rm 49; sort ) < 49) \
<((rm 50; sort ) < 50) ) \
<(sort -m \
<((rm 51; sort ) < 51) \
<((rm 52; sort ) < 52) ) ) \
<(sort -m \
<(sort -m \
<((rm 53; sort ) < 53) \
<((rm 54; sort ) < 54) ) \
<(sort -m \
<((rm 55; sort ) < 55) \
<((rm 56; sort ) < 56) ) ) ) \
<(sort -m \
<(sort -m \
<(sort -m \
<((rm 57; sort ) < 57) \
<((rm 58; sort ) < 58) ) \
<(sort -m \
<((rm 59; sort ) < 59) \
<((rm 60; sort ) < 60) ) ) \
<(sort -m \
<(sort -m \
<((rm 61; sort ) < 61) \
<((rm 62; sort ) < 62) ) \
<(sort -m \
<((rm 63; sort ) < 63) \
<((rm 64; sort ) < 64) ) ) ) ) ) |
md5sum
withcat
:
#!/bin/bash
export LC_ALL=C
sort -m \
<(sort -m \
<(sort -m \
<(sort -m \
<(sort -m \
<(sort -m \
<((rm 1; sort ) < 1) \
<((rm 2; sort ) < 2) | cat) \
<(sort -m \
<((rm 3; sort ) < 3) \
<((rm 4; sort ) < 4) | cat) | cat) \
<(sort -m \
<(sort -m \
<((rm 5; sort ) < 5) \
<((rm 6; sort ) < 6) | cat) \
<(sort -m \
<((rm 7; sort ) < 7) \
<((rm 8; sort ) < 8) | cat) | cat) | cat) \
<(sort -m \
<(sort -m \
<(sort -m \
<((rm 9; sort ) < 9) \
<((rm 10; sort ) < 10) | cat) \
<(sort -m \
<((rm 11; sort ) < 11) \
<((rm 12; sort ) < 12) | cat) | cat) \
<(sort -m \
<(sort -m \
<((rm 13; sort ) < 13) \
<((rm 14; sort ) < 14) | cat) \
<(sort -m \
<((rm 15; sort ) < 15) \
<((rm 16; sort ) < 16) | cat) | cat) | cat) | cat) \
<(sort -m \
<(sort -m \
<(sort -m \
<(sort -m \
<((rm 17; sort ) < 17) \
<((rm 18; sort ) < 18) | cat) \
<(sort -m \
<((rm 19; sort ) < 19) \
<((rm 20; sort ) < 20) | cat) | cat) \
<(sort -m \
<(sort -m \
<((rm 21; sort ) < 21) \
<((rm 22; sort ) < 22) | cat) \
<(sort -m \
<((rm 23; sort ) < 23) \
<((rm 24; sort ) < 24) | cat) | cat) | cat) \
<(sort -m \
<(sort -m \
<(sort -m \
<((rm 25; sort ) < 25) \
<((rm 26; sort ) < 26) | cat) \
<(sort -m \
<((rm 27; sort ) < 27) \
<((rm 28; sort ) < 28) | cat) | cat) \
<(sort -m \
<(sort -m \
<((rm 29; sort ) < 29) \
<((rm 30; sort ) < 30) | cat) \
<(sort -m \
<((rm 31; sort ) < 31) \
<((rm 32; sort ) < 32) | cat) | cat) | cat) | cat) | cat) \
<(sort -m \
<(sort -m \
<(sort -m \
<(sort -m \
<(sort -m \
<((rm 33; sort ) < 33) \
<((rm 34; sort ) < 34) | cat) \
<(sort -m \
<((rm 35; sort ) < 35) \
<((rm 36; sort ) < 36) | cat) | cat) \
<(sort -m \
<(sort -m \
<((rm 37; sort ) < 37) \
<((rm 38; sort ) < 38) | cat) \
<(sort -m \
<((rm 39; sort ) < 39) \
<((rm 40; sort ) < 40) | cat) | cat) | cat) \
<(sort -m \
<(sort -m \
<(sort -m \
<((rm 41; sort ) < 41) \
<((rm 42; sort ) < 42) | cat) \
<(sort -m \
<((rm 43; sort ) < 43) \
<((rm 44; sort ) < 44) | cat) | cat) \
<(sort -m \
<(sort -m \
<((rm 45; sort ) < 45) \
<((rm 46; sort ) < 46) | cat) \
<(sort -m \
<((rm 47; sort ) < 47) \
<((rm 48; sort ) < 48) | cat) | cat) | cat) | cat) \
<(sort -m \
<(sort -m \
<(sort -m \
<(sort -m \
<((rm 49; sort ) < 49) \
<((rm 50; sort ) < 50) | cat) \
<(sort -m \
<((rm 51; sort ) < 51) \
<((rm 52; sort ) < 52) | cat) | cat) \
<(sort -m \
<(sort -m \
<((rm 53; sort ) < 53) \
<((rm 54; sort ) < 54) | cat) \
<(sort -m \
<((rm 55; sort ) < 55) \
<((rm 56; sort ) < 56) | cat) | cat) | cat) \
<(sort -m \
<(sort -m \
<(sort -m \
<((rm 57; sort ) < 57) \
<((rm 58; sort ) < 58) | cat) \
<(sort -m \
<((rm 59; sort ) < 59) \
<((rm 60; sort ) < 60) | cat) | cat) \
<(sort -m \
<(sort -m \
<((rm 61; sort ) < 61) \
<((rm 62; sort ) < 62) | cat) \
<(sort -m \
<((rm 63; sort ) < 63) \
<((rm 64; sort ) < 64) | cat) | cat) | cat) | cat) | cat) | cat |
md5sum
唯一的区别是 in withcat
everysort -m
被通过管道传输到cat
.
“猫的无用用途”——人们会让你相信withcat
会比 慢nocat
。然而,事实恰恰相反:
$ time bash nocat
c933d81faea7b8dec8eb64ca0b044d74 -
real 3m40.854s
user 2m48.687s
sys 0m49.135s
$ time bash withcat
c933d81faea7b8dec8eb64ca0b044d74 -
real 2m21.812s
user 2m16.651s
sys 1m36.135s
该测试在 64 核机器上运行,不执行任何其他操作。一切都在 RAM 中(所以这不是由于磁盘速度慢造成的)。每个测试运行3次,上面给出了最佳时间。所有三项测试均在最佳时间的 5 秒内完成(因此这并非侥幸)。
为什么通过管道将输出传输到 会更快cat
?
编辑
小组输入是否cat
会更大块?和/或是否sort
刷新每行的输出?
为了测试这个我尝试过:
$ strace -ff sort -m <(sort 1) <(sort 2) 2>fromsort | cat >/dev/null
$ strace -ff sort -m <(sort 1 | cat ) <(sort 2 | cat) 2>fromcat | cat >/dev/null
如果cat
将其分成更大的块,我们期望read
返回更大的块。但它没有:
$ grep -E 'read|write' fromsort |field 1,5|sort | uniq -c
1 openat(AT_FDCWD, 3
8 pread64(3, =
1 read(3, 3771
40989 read(3, 4096
2 read(3, 832
1 read(3, unknown
1 read(4, 0
1 read(4, 2241
40959 read(4, 4096
1 write(1, 1916
81949 write(1, 4096
$ grep -E 'read|write' fromcat |field 1,5|sort | uniq -c
1 openat(AT_FDCWD, 3
8 pread64(3, =
1 read(3, 3771
40989 read(3, 4096
2 read(3, 832
1 read(3, unknown
1 read(4, 2241
40959 read(4, 4096
1 read(4, unknown
1 write(1, 1916
81949 write(1, 4096
在这两种情况下,read
和write
都是 4K。
(顺便,sort
做如果从文件而不是从管道读取,则读取(更多)更大的块,但这里不是这种情况)。
编辑2
上述的目的是为了证明附加cat
并不总是无用的;并找出造成这种情况的原因。
目标不是对数据进行排序。
但如果你的目标曾是要对数据进行排序,为什么不直接使用sort
内置的--parallel
?
默认情况下sort
似乎--parallel 8
在 64 核机器上使用。top
显示它使用了高达 800% 的 CPU。您可以强制它使用 64 核--parallel 64
:
$ time sort {1..64} | md5sum
real 9m4.005s
user 29m56.454s
sys 5m49.560s
$ time sort --parallel 64 {1..64} | md5sum
real 6m50.332s
user 35m55.040s
sys 11m37.609s
所以 GNU 排序--parallel
比上面慢得多。以上内容现已提供parsort
:http://git.savannah.gnu.org/cgit/parallel.git/tree/src/parsort
答案1
这绝不是“猫的无用之举”。
some_command | cat | some_command
这不是传统意义上的“猫的无用之用”,通常是由于对外壳的无知而产生的。相反,这似乎是有意尝试利用猫的动态来做某事。在这种情况下,我相信它是缓存。
我的第二个想法
即使读取和写入的大小没有任何不同,也有一些可能无法检测到的事情也可能在起作用。
首先(这非常重要):为什么处理已排序的数组比处理未排序的数组更快?。如果您执行任何更改 CPU 处理此操作的顺序,则时间可能会改变。如果cat
成功地使每次sort
运行时间更长而不挂起(并切换到不同的进程),那么这可能会极大地影响 CPU 的分支预测并导致更长或更短的时间。
其次,即使读取的数量和大小不受影响,任务必须挂起(阻止)的次数也可能不同。这本身可能会增加或减少开销。因此,即使读取和写入的大小相同, (缓存)层也可能会减少每次读取和写入cat
的次数。read()
write()
Cat 可能只是强制排序等待更长时间,从而有更多可用的工作,而无需挂起并减少每个进程阻塞的次数。这将很难被发现。
我的第一个想法
我的期望是,如果您将两个版本放入各自的脚本中并strace -f
在每个脚本上运行,您将在带有 cat 的示例中看到更少的读或/写调用。至少,我希望使用cat
.我的期望sort
是它会写入单行并且不会在内部缓冲太多。事实上,我希望它能read()
在足够大的块中,但只能write()
在单行中。这意味着它的设计不适合自身的管道传输。
正如 laktak 指出的那样他的回答, cat 以 128KB 的块读取(看这里)但管道通常只缓冲 64KB。如果我是对的,那么当 cat 挂起等待 aread()
完成时,这将为写入sort
操作提供一个大的(128 + 64 KB)缓冲区,无需挂起即可写入。到恢复时,cat
将有大量数据(比sort
单次写入发送的数据多得多)传递到下一个sort
。因此,下一个sort
可以从中读取很多内容而不会被暂停。
我也怀疑添加最接近的文件层cat
对性能几乎没有影响或负面影响。这些文件已经缓存在您的 RAM 磁盘上。但是调用之间的层sort
将充当缓冲区,并且应该减少数量。这才是真正的“cat 无用用途”,即那些使用 cat 读取文件的情况。这就是以下形式:
cat some_file | some_command
一个有趣的实验
我很想知道是否可以通过增加管道上的缓冲区大小来产生相同的效果。如果您使用正确的编程语言(而不是 shell)设置相同的管道。例如,在 C 中,您可以使用pipe()
、dup2()
、fork()
和首先exec()
调用每个管道来创建管道ioctl()
以提高缓冲区大小(请参阅管道容量)
答案2
我的猜测是,您对 cat 的使用会限制每个命令的吞吐量,从而使它们并行运行得更快。
cat
读取你的数据128KB 的块。由于我无法重现您的测试,您能否尝试替换您的用法cat
来dd
证明我是对还是错?
dd status=none bs=128K
应该具有相同的效果cat
- 尝试增加/减少块大小并比较结果。