从概念上理解内存映射
Understanding memory mapping conceptually
我已经在 cs.stackexchange.com 上问过这个问题,但还是决定在这里 post 提出这个问题。
我已经阅读了一些关于堆栈交换的博客和问题,但我无法理解内存映射文件的真正缺点是什么。我看到经常列出以下内容:
您不能使用 32 位地址 space 内存映射大文件 (>4GB)。现在这对我来说很有意义。
我想到的一个缺点是,如果太多文件被内存映射,这可能会导致可用系统资源(内存)减少 => 可能导致页面被逐出 => 可能会出现更多页面错误.因此,在决定将哪些文件映射到内存及其访问模式时需要谨慎。
内核映射和数据结构的开销 - according to Linus Torvalds。我什至不会尝试质疑这个前提,因为我对 Linux 内核的内部结构知之甚少。 :)
如果应用程序试图读取未加载到页面缓存中的文件部分,它(应用程序)将以页面错误的形式招致惩罚,这反过来意味着增加了操作的 I/O 延迟。
问题 #1:标准文件 I/O 操作不也是如此吗?如果应用程序试图从尚未缓存的文件部分读取,将导致系统调用导致内核从设备加载相关 page/block。最重要的是,页面需要被复制回 user-space 缓冲区。
这里的问题是页面错误在某种程度上比一般的系统调用更昂贵——我对 Linus Torvalds says here 的解释?是因为页面错误正在阻塞 => 线程没有被安排关闭 CPU => 我们在浪费宝贵的时间吗?还是我遗漏了什么?
- 内存映射文件不支持异步I/O。
问题 #2:支持内存映射文件的异步 I/O 是否存在架构限制,或者只是没有人愿意这样做?
问题 #3:隐约相关,但我对 this article 的解释是内核可以预读标准 I/O(即使没有 fadvise())但不会预读内存映射文件(除非使用 madvice() 发出咨询)。这是准确的吗?如果这个说法实际上是正确的,这就是为什么标准 I/O 的系统调用可能更快,而不是几乎总是会导致页面错误的内存映射文件?
QUESTION #1: Isn't this the case for a standard file I/O operation as well? If an application tries to read from a part of a file that is not yet cached, it will result in a syscall that will cause the kernel to load the relevant page/block from the device. And on top of that, the page needs to be copied back to the user-space buffer.
你对缓冲区执行 read
,I/O 设备会将其复制到那里。还有 async 读取或 AIO,其中数据将在设备提供数据时由内核在后台传输。您可以对线程和 read
做同样的事情。对于 mmap
情况,您无法控制或不知道页面是否已映射。 read
的情况更为明确。这是从
ssize_t read(int fd, void *buf, size_t count);
您指定了 buf
和 count
。您可以明确地将数据放置在程序中您想要的位置。作为程序员,您可能知道数据不会被再次使用。对 read
的后续调用可以重用上次调用中相同的 buf
。这有很多好处;最容易看到的是更少的内存使用(或至少地址 space 和 MMU 表)。 mmap
不知道某个页面以后是否还会被访问。 mmap
不知道只有页面中的某些数据是感兴趣的。因此,read
更明确。
假设您在磁盘上有 4096 条大小为 4095 字节的记录。您需要对两个随机记录进行 read/look 并对它们执行操作。对于read
,你可以用malloc()
分配两个4095缓冲区或者使用static char buffer[2][4095]
数据。 mmap()
必须为每个记录映射平均 8192 字节以填充两页或总共 16k。访问每个 mmap
记录时,记录跨越两页。这会导致每个记录访问出现两次 页面错误 。此外,内核必须分配四个 TLB/MMU 页来保存数据。
或者,如果 read
到顺序缓冲区,只需要两个页面,只有两个系统调用 (read
)。此外,如果对记录的计算量很大,缓冲区的位置将使它比 mmap
数据快得多(CPU 缓存命中)。
And on top of that, the page needs to be copied back to the user-space buffer.
此副本可能没有您认为的那么糟糕。 CPU 将缓存数据,以便下一次访问不必从主内存重新加载,速度可能比 L1 CPU 缓存慢 100 倍。
在上面的例子中,mmap
可以接管read
的两倍。
Is the concern here that page-faults are somehow more expensive than syscalls in general - my interpretation of what Linus Torvalds says here? Is it because page-faults are blocking => the thread is not scheduled off the CPU => we are wasting precious time? Or is there something I'm missing here?
我认为重点是您无法控制 mmap
。您 mmap
文件并且不知道是否有任何部分在内存中。如果您只是随机访问该文件,那么它会继续从磁盘读回它,并且您可能会在不知情的情况下根据访问模式进行抖动。如果访问是纯顺序的,那么乍一看似乎也好不到哪儿去。然而,通过重新读取一个新的 chunk 到相同的用户缓冲区,L1/L2 CPU 缓存和 CPU 的 TLB 将得到更好的利用;对于您的流程和系统中的其他流程。如果您将所有块读取到一个唯一的缓冲区并按顺序处理,那么它们将大致相同(请参见下面的注释)。
QUESTION #2: Is there an architectural limitation with supporting async I/O for memory mapped files, or is it just that it no one got around to doing it?
mmap
已经和AIO类似了,只是固定大小为4k。也就是说,完整的 mmap
文件不需要在内存中就可以开始对其进行操作。从功能上讲,它们是获得相似效果的不同机制。它们在架构上不同。
QUESTION #3: Vaguely related, but my interpretation of this article is that the kernel can read-ahead for standard I/O (even without fadvise()) but does not read-ahead for memory mapped files (unless issued an advisory with madvice()). Is this accurate? If this statement is in-fact true, is that why syscalls for standard I/O maybe faster, as opposed to a memory mapped file which will almost always cause a page-fault?
read
的糟糕编程可能与 mmap
一样糟糕。 mmap
可以使用 madvise
。它与使 mmap
工作所必须发生的所有 Linux MM 内容更相关。这完全取决于您的用例;根据访问模式,两者都可以更好地工作。我认为 Linus 只是在说两者都不是灵丹妙药。
例如,如果您 read
到一个比系统拥有的内存更多的缓冲区,并且您使用与 mmap
执行相同类型处理的交换,您会变得更糟。您可能有一个没有交换的系统,并且 mmap
用于随机读取访问会很好,并且允许您管理比实际内存更大的文件。使用 read
执行此设置将需要更多代码,这通常意味着更多错误,或者如果您天真,您只会收到一条 OOM 终止消息。注意 但是,如果访问是顺序的,read
没有那么多代码,它可能比 mmap
.
快
额外 read
好处
对于某些人来说,read
提供 套接字 和 管道 的使用。此外,字符设备,例如 ttyS0,只能与 read
一起使用。如果您编写一个从命令行获取文件名的命令行程序,这将很有用。如果您使用 mmap
构建,可能很难支持这些文件。
我已经在 cs.stackexchange.com 上问过这个问题,但还是决定在这里 post 提出这个问题。
我已经阅读了一些关于堆栈交换的博客和问题,但我无法理解内存映射文件的真正缺点是什么。我看到经常列出以下内容:
您不能使用 32 位地址 space 内存映射大文件 (>4GB)。现在这对我来说很有意义。
我想到的一个缺点是,如果太多文件被内存映射,这可能会导致可用系统资源(内存)减少 => 可能导致页面被逐出 => 可能会出现更多页面错误.因此,在决定将哪些文件映射到内存及其访问模式时需要谨慎。
内核映射和数据结构的开销 - according to Linus Torvalds。我什至不会尝试质疑这个前提,因为我对 Linux 内核的内部结构知之甚少。 :)
如果应用程序试图读取未加载到页面缓存中的文件部分,它(应用程序)将以页面错误的形式招致惩罚,这反过来意味着增加了操作的 I/O 延迟。
问题 #1:标准文件 I/O 操作不也是如此吗?如果应用程序试图从尚未缓存的文件部分读取,将导致系统调用导致内核从设备加载相关 page/block。最重要的是,页面需要被复制回 user-space 缓冲区。
这里的问题是页面错误在某种程度上比一般的系统调用更昂贵——我对 Linus Torvalds says here 的解释?是因为页面错误正在阻塞 => 线程没有被安排关闭 CPU => 我们在浪费宝贵的时间吗?还是我遗漏了什么?
- 内存映射文件不支持异步I/O。
问题 #2:支持内存映射文件的异步 I/O 是否存在架构限制,或者只是没有人愿意这样做?
问题 #3:隐约相关,但我对 this article 的解释是内核可以预读标准 I/O(即使没有 fadvise())但不会预读内存映射文件(除非使用 madvice() 发出咨询)。这是准确的吗?如果这个说法实际上是正确的,这就是为什么标准 I/O 的系统调用可能更快,而不是几乎总是会导致页面错误的内存映射文件?
QUESTION #1: Isn't this the case for a standard file I/O operation as well? If an application tries to read from a part of a file that is not yet cached, it will result in a syscall that will cause the kernel to load the relevant page/block from the device. And on top of that, the page needs to be copied back to the user-space buffer.
你对缓冲区执行 read
,I/O 设备会将其复制到那里。还有 async 读取或 AIO,其中数据将在设备提供数据时由内核在后台传输。您可以对线程和 read
做同样的事情。对于 mmap
情况,您无法控制或不知道页面是否已映射。 read
的情况更为明确。这是从
ssize_t read(int fd, void *buf, size_t count);
您指定了 buf
和 count
。您可以明确地将数据放置在程序中您想要的位置。作为程序员,您可能知道数据不会被再次使用。对 read
的后续调用可以重用上次调用中相同的 buf
。这有很多好处;最容易看到的是更少的内存使用(或至少地址 space 和 MMU 表)。 mmap
不知道某个页面以后是否还会被访问。 mmap
不知道只有页面中的某些数据是感兴趣的。因此,read
更明确。
假设您在磁盘上有 4096 条大小为 4095 字节的记录。您需要对两个随机记录进行 read/look 并对它们执行操作。对于read
,你可以用malloc()
分配两个4095缓冲区或者使用static char buffer[2][4095]
数据。 mmap()
必须为每个记录映射平均 8192 字节以填充两页或总共 16k。访问每个 mmap
记录时,记录跨越两页。这会导致每个记录访问出现两次 页面错误 。此外,内核必须分配四个 TLB/MMU 页来保存数据。
或者,如果 read
到顺序缓冲区,只需要两个页面,只有两个系统调用 (read
)。此外,如果对记录的计算量很大,缓冲区的位置将使它比 mmap
数据快得多(CPU 缓存命中)。
And on top of that, the page needs to be copied back to the user-space buffer.
此副本可能没有您认为的那么糟糕。 CPU 将缓存数据,以便下一次访问不必从主内存重新加载,速度可能比 L1 CPU 缓存慢 100 倍。
在上面的例子中,mmap
可以接管read
的两倍。
Is the concern here that page-faults are somehow more expensive than syscalls in general - my interpretation of what Linus Torvalds says here? Is it because page-faults are blocking => the thread is not scheduled off the CPU => we are wasting precious time? Or is there something I'm missing here?
我认为重点是您无法控制 mmap
。您 mmap
文件并且不知道是否有任何部分在内存中。如果您只是随机访问该文件,那么它会继续从磁盘读回它,并且您可能会在不知情的情况下根据访问模式进行抖动。如果访问是纯顺序的,那么乍一看似乎也好不到哪儿去。然而,通过重新读取一个新的 chunk 到相同的用户缓冲区,L1/L2 CPU 缓存和 CPU 的 TLB 将得到更好的利用;对于您的流程和系统中的其他流程。如果您将所有块读取到一个唯一的缓冲区并按顺序处理,那么它们将大致相同(请参见下面的注释)。
QUESTION #2: Is there an architectural limitation with supporting async I/O for memory mapped files, or is it just that it no one got around to doing it?
mmap
已经和AIO类似了,只是固定大小为4k。也就是说,完整的 mmap
文件不需要在内存中就可以开始对其进行操作。从功能上讲,它们是获得相似效果的不同机制。它们在架构上不同。
QUESTION #3: Vaguely related, but my interpretation of this article is that the kernel can read-ahead for standard I/O (even without fadvise()) but does not read-ahead for memory mapped files (unless issued an advisory with madvice()). Is this accurate? If this statement is in-fact true, is that why syscalls for standard I/O maybe faster, as opposed to a memory mapped file which will almost always cause a page-fault?
read
的糟糕编程可能与 mmap
一样糟糕。 mmap
可以使用 madvise
。它与使 mmap
工作所必须发生的所有 Linux MM 内容更相关。这完全取决于您的用例;根据访问模式,两者都可以更好地工作。我认为 Linus 只是在说两者都不是灵丹妙药。
例如,如果您 read
到一个比系统拥有的内存更多的缓冲区,并且您使用与 mmap
执行相同类型处理的交换,您会变得更糟。您可能有一个没有交换的系统,并且 mmap
用于随机读取访问会很好,并且允许您管理比实际内存更大的文件。使用 read
执行此设置将需要更多代码,这通常意味着更多错误,或者如果您天真,您只会收到一条 OOM 终止消息。注意 但是,如果访问是顺序的,read
没有那么多代码,它可能比 mmap
.
额外 read
好处
对于某些人来说,read
提供 套接字 和 管道 的使用。此外,字符设备,例如 ttyS0,只能与 read
一起使用。如果您编写一个从命令行获取文件名的命令行程序,这将很有用。如果您使用 mmap
构建,可能很难支持这些文件。