在Ubuntu(Linux操作系统)中将一个巨大的文件读入C++向量

9
在我的C ++程序中(在Linux(Ubuntu 14.4)上运行),我需要使用C ++向量完全缓冲90 GB的文件,而我只有125 GB的内存。
当我一块一块地读取文件时,在Linux中缓存的内存使用不断增加,这导致超过128 GB内存的50%,然后空闲内存很容易变成低于50 GB。
              total        used        free      shared  buff/cache   available
Mem:            125          60         0           0          65         65

交换区:255 0 255

我发现可用内存变为零,文件读取过程几乎停止,我不得不手动运行:

echo 3 | sudo tee /proc/sys/vm/drop_caches

清除缓存内存,以便文件读取过程恢复。我知道缓存内存是为了再次快速读取文件的。我的问题是如何避免手动运行drop cache命令以确保文件读取过程成功完成?


2
你看过这个吗:https://serverfault.com/questions/288319/linux-not-freeing-large-disk-cache-when-memory-demand-goes-up 你正在寻找一个问题的解决方法,而这个问题本不应该存在。当有其他内存压力时,磁盘缓存不应该增长那么大。此外,预设向量大小可能会防止磁盘缓存增长过大。 - xaxxon
5
只需将该文件映射到内存即可。 - user7860670
@xaxxon 我预计这种方法能够提供相同或略微更好的性能。 - user7860670
1
如果在mmap调用中使用了MAP_POPULATE标志,则所有映射的数据都将被预取到内存中。 - user7860670
@VTT 非常酷,我学到了东西 :) 我建议也使用 mmap,现在我知道这个,因为它很可能针对您运行的任何平台进行高度优化 - 比用户空间中编写的任何 C++ 代码更好地调整。 尽管如此,我仍然非常感兴趣磁盘缓存为什么会增长,这可能值得您将其发布到 poweruser(在检查我上面发布的链接中的内容以确保它不是其中之一后)。 - xaxxon
显示剩余13条评论
1个回答

3
由于您只是简单地流式传输数据并且从未重新读取它,因此页面缓存对您没有任何好处。实际上,考虑到您通过页面缓存推送的数据量和应用程序的内存压力,否则有用的数据可能会从页面缓存中驱逐出去,并且您的系统性能会因此受到影响。
因此,在读取数据时不要使用缓存。使用直接IO。根据Linux open() man page

O_DIRECT (since Linux 2.4.10)

Try to minimize cache effects of the I/O to and from this file. In general this will degrade performance, but it is useful in special situations, such as when applications do their own caching. File I/O is done directly to/from user- space buffers. The O_DIRECT flag on its own makes an effort to transfer data synchronously, but does not give the guarantees of the O_SYNC flag that data and necessary metadata are transferred. To guarantee synchronous I/O, O_SYNC must be used in addition to O_DIRECT. See NOTES below for further discussion.

...

NOTES

...

O_DIRECT

The O_DIRECT flag may impose alignment restrictions on the length and address of user-space buffers and the file offset of I/Os. In Linux alignment restrictions vary by filesystem and kernel version and might be absent entirely. However there is currently no filesystem-independent interface for an application to discover these restrictions for a given file or filesystem. Some filesystems provide their own interfaces for doing so, for example the XFS_IOC_DIOINFO operation in xfsctl(3).

Under Linux 2.4, transfer sizes, and the alignment of the user buffer and the file offset must all be multiples of the logical block size of the filesystem. Since Linux 2.6.0, alignment to the logical block size of the underlying storage (typically 512 bytes) suffices. The logical block size can be determined using the ioctl(2) BLKSSZGET operation or from the shell using the command:

      blockdev --getss

...

由于您不会反复读取数据,直接IO很可能会稍微提高性能,因为数据将直接从磁盘传输到应用程序的内存中,而不是从磁盘传输到页面缓存,然后再传输到应用程序的内存中。
使用低级别的C风格I/O,使用open()/read()/close(),并使用O_DIRECT标志打开文件。
int fd = ::open( filename, O_RDONLY | O_DIRECT );

这将导致数据直接读入应用程序的内存,而不会缓存在系统的页面缓存中。
您需要使用对齐的内存进行read()操作,因此您需要类似于以下内容才能实际读取数据:
char *buffer;
size_t pageSize = sysconf( _SC_PAGESIZE );
size_t bufferSize = 32UL * pageSize;

int rc = ::posix_memalign( ( void ** ) &buffer, pageSize, bufferSize );

posix_memalign()是一个符合POSIX标准的函数,它返回一个按要求对齐的内存指针。页面对齐缓冲区通常已经足够了,但是将其对齐到巨大页面大小(在x86-64上为2MiB)将提示内核你想要透明巨大页面来分配内存,当你以后读取它时,访问缓冲区更有效率。

ssize_t bytesRead = ::read( fd, buffer, bufferSize );

没有您的代码,我无法确定如何将数据从buffer传输到您的std::vector中,但这不应该很难。可能有一些方法可以使用某种类型的C++流来包装C样式的低级文件描述符,并配置该流以使用适用于直接IO的内存对齐方式。
如果您想看到区别,请尝试这样做:
echo 3 | sudo tee /proc/sys/vm/drop_caches
dd if=/your/big/data/file of=/dev/null bs=32k

测量时间。然后查看页面缓存中的数据量。

接着执行以下操作:

echo 3 | sudo tee /proc/sys/vm/drop_caches
dd if=/your/big/data/file iflag=direct of=/dev/null bs=32k

在此之后,检查页面缓存中的数据量...

您可以尝试不同的块大小,以查看在您的硬件和文件系统上哪种效果最好。

请注意,直接IO非常依赖于实现。执行直接IO的要求在不同的文件系统之间可能会有很大差异,并且性能可能会因IO模式和特定硬件而发生显著变化。 大多数情况下,这些依赖关系都不值得,但是通常情况下,唯一简单的用途是在不重新读取/重写任何部分数据的情况下流式传输大型文件。


你为什么喜欢这个解决方案胜过mmap? - xaxxon
1
@xaxxon 它比 mmap() 更快,而且 mmap() 不会减少内存压力。mmap() 经常被当作某种魔法弹药,但实际上并非如此。当仅需流式传输一次大文件时,它是读取数据的最糟糕方式之一。阅读 Linus Torvalds 的观点 [我加粗了一些字]: “人们喜欢使用 mmap() 和其他方式来玩弄页表以优化复制操作,有时候这样做是值得的。然而,玩弄虚拟内存映射本身就非常昂贵。” - Andrew Henle
继续之前,这就是引发Torvalds回应的原因:“我非常沮丧地发现,在我的系统上,mmap/mlock方法所需的时间比读取解决方案多了3倍。在我看来,mmap/mlock应该至少与读取一样快。欢迎评论。” - Andrew Henle
每次都在重写相同的目标缓冲区,因此两种情况都没有测试触及新内存的开销。似乎read(2)中的copy-to-user也必须执行一些TLB失效或类似操作,因为我在读取版本中看到了很多TLB未命中,特别是当我通过将缓存阻塞大小小于4k(因此在同一页上执行多个read()调用)来探索现象时。我认为我最好的猜测是read(2)会使目标缓冲区的页面的TLB条目无效。 - Peter Cordes
1
@PeterCordes 对于OP的用例,该文件将适合RAM。正如所述,它确实使用了大量RAM,因此如果存在其他重要的内存需求,则对于OP来说可能并不完全正确。鉴于OP对内存使用的关注,我提供了这个答案作为最小化内存使用的一种方式。除了文件支持的映射无法使用hugepages,据我所知。我相信这是正确的。但是,我找不到我所指的问题。 - Andrew Henle
显示剩余10条评论

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