管道如何限制内存使用量?


36

Brian Kernighan在此视频中解释了贝尔实验室基于内存限制而对小语言/程序的早期吸引力

一台大机器将是64 k字节,即K,而不是M或G,这意味着任何单个程序都不会太大,因此自然会倾向于编写小程序,然后编写管道机制,基本上是输入输出重定向,因此可以将一个程序链接到另一个程序。

但是考虑到数据必须存储在RAM中才能在程序之间传输,因此我不理解这如何限制内存使用。

来自维基百科

在大多数类似Unix的系统中,管道的所有进程都是同时启动的[强调我的],并适当地连接它们的流,并由调度程序以及计算机上运行的所有其他进程进行管理。其中一个重要方面是将Unix管道与其他管道实现区分开来,它是缓冲的概念:例如,发送程序每秒可以产生5000个字节,而接收程序每秒只能接受100个字节,但没有。数据丢失。而是将发送程序的输出保存在缓冲区中。当接收程序准备读取数据时,管道中的下一个程序将从缓冲区读取数据。在Linux中,缓冲区的大小为65536字节(64KB)。如果需要,可以使用称为bfr的开源第三方过滤器来提供更大的缓冲区。

这使我更加困惑,因为这完全破坏了小程序的目的(尽管它们会在一定程度上模块化)。

作为第一个问题(内存限制取决于大小数据的问题),我唯一能想到的解决方案是,那时根本就不会计算大型数据集,而真正的问题管道本应解决的是程序本身所需的内存量。但是考虑到Wikipedia引号中的粗体字,即使这样也使我感到困惑:因为一次不会执行一个程序。

如果使用了临时文件,那么所有这些都将非常有意义,但是据我了解,管道不会写入磁盘(除非使用交换)。

例:

sed 'simplesubstitution' file | sort | uniq > file2

对我来说sed很明显,正在读取文件并逐行吐出。但是sort,正如BK在链接的视频中指出的那样,它是一个句号,因此必须将所有数据读入内存(或对吗?),然后将其传递给uniq,(我认为)这将是一个一次在线程序。但是在第一个和第二个管道之间,所有数据都必须存储在内存中,不是吗?


1
unless swap is used内存不足时总是使用swap
edc65

Answers:


44

数据不需要存储在RAM中。如果读者不在或无法跟上,管道会阻塞其作者。在Linux(以及我想象的大多数其他实现)下,有一些缓冲,但这不是必需的。如mtraceurJdeBP所述(请参阅后者的答案),这是Unix的早期版本,它缓冲了到磁盘的管道,这就是它们帮助限制内存使用的方式:一个处理管道可以拆分为多个小程序,每个小程序可以在磁盘缓冲区的范围内处理一些数据。小程序占用更少的内存,管道的使用意味着可以序列化处理:第一个程序将运行,填充其输出缓冲区,被挂起,然后第二个程序将被调度,处理缓冲区等。现代系统是有序的比早期的Unix系统大很多,并且可以并行运行许多管道;但是对于大量数据,您仍然会看到类似的效果(这种技术的变体用于“大数据”处理)。

在您的示例中

sed 'simplesubstitution' file | sort | uniq > file2

sedfile必要的位置读取数据,然后在sort准备好读取数据时将其写入;如果sort尚未准备好,则写块。数据确实确实确实存在于内存中,但这是特定于的sort,并且sort准备处理任何问题(它将使用临时文件,因为要排序的数据量太大)。

您可以通过运行来查看阻止行为

strace seq 1000000 -1 1 | (sleep 120; sort -n)

这会产生大量数据,并将其通过管道传输到一个过程,该过程在开始的两分钟之内无法读取任何内容。您会看到许多write操作正在进行,但是很快seq就会停止并等待两分钟,这被内核阻止了(write系统调用正在等待)。


13
该回答可能会从进一步解释为什么将程序拆分为许多小程序以节省内存的使用中受益:一个程序必须能够容纳内存才能运行,但只能运行当前正在运行的程序。在Unix早期,所有其他程序都被换出到磁盘上,一次只能将一个程序换成实际的RAM。因此,CPU将运行一个程序,该程序将写入管道(该管道当时位于磁盘上),换出该程序,然后换入从管道读取的程序。将逻辑并行装配线转换为增量序列化执行的绝佳方法。
mtraceur

6
@malan:可以启动多个进程,这些进程可以同时处于可运行状态。但是在任何给定时间,每个物理CPU上最多只能执行一个进程,内核进程调度程序的工作就是为每个可运行的进程分配CPU时间的“片段”。在现代系统中,可运行但当前尚未调度的CPU时间片的进程通常在等待下一个切片时仍驻留在内存中,但是允许内核将任何进程的内存分页到磁盘,然后再次将其分页到内存中。觉得方便。(在此处提供一些详细信息。)
Daniel Pryden

5
管道两侧的进程可以像协同例程一样有效地工作:一侧进行写操作,直到它填满缓冲区和写块为止,此时,该进程对其剩余的时间片无法执行任何操作,并且进入IO等待模式。然后,操作系统将时间片的其余部分(或另一个即将到来的时间片)提供给读取端,读取端将读取直到缓冲区和下一个读取块中没有剩余的内容为止,此时读取器进程无法对其余部分进行任何处理。它的时间片,并返回到OS。数据一次通过管道传递一个缓冲区的价值。
Daniel Pryden

6
@malan这些程序在概念上在所有Unix系统上“同时”启动,只是在具有足够RAM来容纳它们的现代多处理器系统上,这意味着它们实际上同时都在RAM中保留,而在可以不能将它们全部同时保存在RAM中,有的则换成磁盘。还要注意,在许多情况下,“内存”是指虚拟内存,它是RAM空间和磁盘上交换空间的总和。Wikipedia专注于概念而不是实现细节,尤其是因为现在真正的Unix做事的方式已经不那么重要了。
mtraceur

2
@malan另外,您看到的矛盾来自“内存”的两种不同含义(RAM vs RAM + swap)。我只是在谈论硬件RAM,在这种情况下,只有CPU当前正在执行的代码才需要适合RAM(这是Kernighan所讨论的决定的影响因素),而在逻辑上执行所有程序由OS在给定的时间(在时间分割之上提供的抽象级别)上,程序只需要适合OS可用的整个虚拟内存,其中包括磁盘上的交换空间。
mtraceur

34

但是考虑到数据必须存储在RAM中才能在程序之间传输,因此我不理解这如何限制内存使用。

这是您的基本错误。Unix的早期版本没有在RAM中保存管道数据。他们将它们存储在光盘上。管道有i节点;在被称为管道设备的磁盘设备上。系统管理员运行了一个程序/etc/config,该程序用于(除其他事项外)指定哪个磁盘是管道设备,哪个磁盘是根设备以及哪个转储设备

仅磁盘上i节点的直接块用于存储的事实限制了待处理数据的数量。这种机制使代码更简单,因为从管道中读取的算法与从常规文件中读取的算法几乎相同,并且由于管道不可搜索且缓冲区是圆形的事实而造成了一些调整。

在1980年代中期至后期,这种机制被其他机制所取代。SCO XENIX获得了“高性能管道系统”,该系统用核心缓冲区代替了i节点。4BSD使未命名的管道成为套接字对。AT&T使用STREAMS机制重新实现了管道。

当然,该sort程序会执行有限的内部32KiB输入块排序(如果32KiB不可用,则可以分配较小的内存量),然后将排序后的结果写入中间stmX??文件,/usr/tmp/然后在中间文件中进行外部合并以提供最终结果输出。

进一步阅读

  • 史蒂夫·佩特(1996)。“进程间通信”。UNIX内部:一种实用方法。艾迪生-韦斯利。ISBN 9780201877212。
  • 莫里斯·巴赫(1987)。“系统调用文件系统”。 Unix操作系统的设计。普伦蒂斯厅。书号0132017571。
  • 史蒂文五世·埃哈特(1986)。“ config(1M)”。 Unix程序员手册:3.系统管理工具。Holt,Rinehart和Winston。ISBN 0030093139.第23–28页。

1

您部分正确,但只是偶然

在您的示例中,确实必须在管道之间“读取”所有数据,但不必将其驻留在内存(包括虚拟内存)中。通常的实现sort可以通过对tempfile进行部分排序并合并来对不适合RAM的数据集进行排序。但是,众所周知的事实是,在读取每个元素之前,您可能无法输出排序后的序列。这很明显。因此,是的,sort只有在从第一个读取完所有内容(并完成了可能对tempfile进行部分排序的所有操作)之后,才能开始输出到第二个管道。但这并不一定要全部保留在RAM中。

但是,这与管道的工作方式无关。管道可以被命名(传统上它们都被命名),这意味着它们仅在文件系统(如文件)中具有位置。而这恰恰是管道曾经使用过的文件(为了优化,写入合并了尽可能多的物理内存可用性)。

如今,管道是一个很小的有限大小的内核缓冲区,数据将复制到该缓冲区中,至少从概念上讲就是这样。如果内核可以提供帮助,则可以通过玩VM技巧来消除副本(例如,从文件进行管道传输通常只会使同一页面可供其他进程读取,因此最终只能是一个读取操作,而不是两个副本,并且没有仍然需要比缓冲区高速缓存已经使用的内存更多的内存,在某些情况下,您也可能会获得100%的零拷贝,或者非常接近。

如果管道很小且尺寸有限,那么如何处理任何未知(可能很大)的数据?这很简单:当没有其他东西适合时,写入将阻塞,直到再次有空间为止。

从前,在内存非常匮乏的时代,许多简单程序的原理最有用。因为,那么,您可以一步一步地完成工作。如今,我敢说,除了一些额外的灵活性外,优点还不至于如此。
但是,管道的执行效率非常高(必须这样做!),因此也没有任何缺点,并且它已经可以正常工作并且已经为人们所习惯,这是已确定的事情,因此无需更改范例。


当您说“管道已命名”(JdeBP似乎说有一个 “管道设备”)时,是否意味着在给定时间可以使用的管道数量受到限制(即,|一个命令可以使用多少次?)
马兰'18

2
我从未见过这样的限制,而且我认为理论上也没有限制。实际上,任何具有文件名的东西都需要一个索引节点,并且索引节点的数量当然是有限的。如果没有其他问题,则与系统上的物理页面数一样。现代系统保证4k原子写入,因此每个管道必须至少拥有一个完整的4k页面,这对您可以拥有的管道数量施加了硬性限制。但是请考虑拥有几GB的RAM ...实际上,这是您永远不会遇到的限制。尝试在终端上键入几百万个管道... :)
Damon
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.