为什么在使用管道进行排序(Linux命令)时速度较慢?

8
我有一个大约8GB的文本文件,需要进行一些简单的筛选,然后对所有行进行排序。我使用一台28核心、SSD和128GB RAM的计算机进行操作。我尝试了以下方法:
方法1
awk '...' myBigFile | sort --parallel = 56 > myBigFile.sorted

方法二
awk '...' myBigFile > myBigFile.tmp
sort --parallel 56 myBigFile.tmp > myBigFile.sorted

令人惊讶的是,方法1需要11.5分钟,而方法2仅需要(0.75 + 1 < 2)分钟。为什么管道排序这么慢?难道不是并行的吗?
编辑
awk和myBigFile并不重要,通过简单地使用seq 1 10000000 | sort --parallel 56(感谢@Sergei Kurenkov),可以重复进行此实验,并且我也观察到在我的机器上使用未经管道处理的版本可以提高六倍的速度。

1
尝试在管道排序时添加-S8G以分配一个8G缓冲区,看看是否有帮助。为了保持一致的测量结果,您可能需要在每次运行之前清除缓存(例如,读取myBigFile后应该将其缓存),尽管这并不能解释如此大的差异。 - JimD.
@JimD,真的很有帮助!现在它比将中间文件写入SSD要慢一些(134秒与105秒),但这不应该更快吗...? - bbvan
在读取管道时,sort 不知道输入的长度。而在读取文件时,它知道文件的大小。此外,该文件被缓存在您拥有 128GB 内存的良好系统上,因此 sort 实际上并不是从磁盘中读取,而是从缓存中读取。所以我认为这种差异可以归因于 sort 知道输入很大并有效地划分工作,而管道中的 sort 必须发现输入很大(如果没有 -S<huge> 选项,它甚至不会这样做)。 - JimD.
此外,sort 命令可以在文件上进行查找,但不能在管道上进行。您需要查看实现代码以确定是否利用了该功能。 - JimD.
@JimD。感谢您提供的详细解释。您能否将其转化为答案,以便我可以接受它? - bbvan
3个回答

7
当从管道中读取数据时,sort 假设文件很小,对于小文件,并行处理是没有帮助的。如果要让 sort 使用并行处理,需要使用 -S 命令告诉它分配一个大的主内存缓冲区。在这种情况下,数据文件大约为 8GB,可以使用 -S8G 命令。然而,在拥有 128GB 主内存的系统上,方法2可能仍然更快。
这是因为方法2中的 sort 可以根据文件大小知道它很大,并且可以在文件中查找(这两个操作在管道中不可能)。此外,由于您的内存比这些文件大小要多得多,所以无需在 awk 退出之前将 myBigFile.tmp 的数据写入磁盘,sort 将能够从缓存而不是磁盘中读取文件。因此,在像您这样具有大量内存的计算机上,方法1和方法2之间的主要区别是,在方法2中,sort 知道文件很大并且可以轻松地分割工作(可能使用查找,但我没有查看实现),而在方法1中,sort 必须发现数据很大,并且不能使用任何并行处理来读取输入,因为它无法查找管道。

你需要同时指定 -S--parallel 吗?还是 sort 足够聪明,知道在没有 --parallel 开关的情况下使用内存? - Hashim Aziz
根据我的经验,仅使用“-S”参数是不够的,从管道中读取数据时,仅使用“--parallel”参数也不足以并行化排序。请参考例如:https://superuser.com/questions/938558/sort-parallel-isnt-parallelizing - JimD.
我对 "在 awk 退出之前,数据无需写入磁盘,sort 就能从缓存中读取文件" 这句话有些困惑,难道不需要将文件写入磁盘才能实现这个功能吗? - Cole
@Cole 不,Linux 将数据写入缓存并将页面标记为脏页。脏页最终会被刷新到磁盘,但是文件读取命中一个被缓存的脏页时,这并非必须。请参见:https://www.oreilly.com/library/view/understanding-the-linux/0596005652/ch15s01.html - JimD.

3

我认为在从管道读取时,sort命令不使用线程。

  1. I have used this command for your first case. And it shows that sort uses only 1 CPU even though it is told to use 4. atop actually also shows that there is only one thread in sort:

    /usr/bin/time -v bash -c "seq 1 1000000 | sort --parallel 4  > bf.txt"
    
  2. I have used this command for your second case. And it shows that sort uses 2 CPU. atop actually also shows that there are four thread in sort:

    /usr/bin/time -v bash -c "seq 1 1000000 > tmp.bf.txt && sort --parallel 4  tmp.bf.txt > bf.txt"
    
在您的第一个场景中,排序是一个I/O绑定任务,它会从标准输入中进行大量的read系统调用。在您的第二个场景中,排序使用mmap系统调用来读取文件,并避免成为I/O绑定任务。
以下是第一个和第二个场景的结果:
$ /usr/bin/time -v bash -c "seq 1 10000000 | sort --parallel 4  > bf.txt"
        Command being timed: "bash -c seq 1 10000000 | sort --parallel 4  > bf.txt"
        User time (seconds): 35.85
        System time (seconds): 0.84
        Percent of CPU this job got: 98%
        Elapsed (wall clock) time (h:mm:ss or m:ss): 0:37.43
        Average shared text size (kbytes): 0
        Average unshared data size (kbytes): 0
        Average stack size (kbytes): 0
        Average total size (kbytes): 0
        Maximum resident set size (kbytes): 9320
        Average resident set size (kbytes): 0
        Major (requiring I/O) page faults: 0
        Minor (reclaiming a frame) page faults: 2899
        Voluntary context switches: 1920
        Involuntary context switches: 1323
        Swaps: 0
        File system inputs: 0
        File system outputs: 459136
        Socket messages sent: 0
        Socket messages received: 0
        Signals delivered: 0
        Page size (bytes): 4096
        Exit status: 0

$ /usr/bin/time -v bash -c "seq 1 10000000 > tmp.bf.txt && sort --parallel 4  tmp.bf.txt > bf.txt"
        Command being timed: "bash -c seq 1 10000000 > tmp.bf.txt && sort --parallel 4  tmp.bf.txt > bf.txt"
        User time (seconds): 43.03
        System time (seconds): 0.85
        Percent of CPU this job got: 175%
        Elapsed (wall clock) time (h:mm:ss or m:ss): 0:24.97
        Average shared text size (kbytes): 0
        Average unshared data size (kbytes): 0
        Average stack size (kbytes): 0
        Average total size (kbytes): 0
        Maximum resident set size (kbytes): 1018004
        Average resident set size (kbytes): 0
        Major (requiring I/O) page faults: 0
        Minor (reclaiming a frame) page faults: 2445
        Voluntary context switches: 299
        Involuntary context switches: 4387
        Swaps: 0
        File system inputs: 0
        File system outputs: 308160
        Socket messages sent: 0
        Socket messages received: 0
        Signals delivered: 0
        Page size (bytes): 4096
        Exit status: 0

2

如果使用管道,您将拥有更多的系统调用。

seq 1000000 | strace sort --parallel=56 2>&1 >/dev/null | grep read | wc -l
2059

如果没有管道,文件将被映射到内存中。

seq 1000000 > input
strace sort --parallel=56 input 2>&1 >/dev/null | grep read | wc -l
33

内核调用在大多数情况下是瓶颈。这就是为什么sendfile被发明的原因。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接