从单个文件同时读取

Posted

技术标签:

【中文标题】从单个文件同时读取【英文标题】:Concurrently read from a single file 【发布时间】:2010-10-23 10:24:56 【问题描述】:

我有以下问题情况。一堆数据被分成 10k 个小文件(每个大约 8-16 kib)。根据用户输入,我必须尽快加载并处理它们。更准确地说,每个数据包可以拆分成100-100k个文件,大约有1k个数据包。不过,它们中的大多数都是较小的。

现在,我正在使用线程池,在每次文件访问时,下一个空闲线程都会打开文件,读取它,然后返回准备显示的数据。随着未来文件数量的增长,我对这种方法不太满意,特别是如果它最终可能会得到大约 100k 或更多的文件(部署它肯定会很有趣;))。

因此,我们的想法是将一个数据包的所有这些小文件组合成一个大文件,然后从中读取。我可以保证它将是只读的,但我不知道将同时访问一个文件的线程数(我知道最大数量)。这将为我提供大约 1000 个大小合适的文件,并且我可以轻松添加新数据包。

问题是:在这种情况下,如何让 1..N 个线程高效地从单个文件中读取?我可以在 Windows 上使用异步 I/O,但是对于小于 64k 的读取,它应该是同步的。内存映射文件不是一个选项,因为预期大小> 1.6 GiB,我仍然需要能够在x86上运行(除非我可以有效地映射一些小部分,阅读它,再次取消映射 - 我的经验内存映射是它带来了与单次读取相比相当多的开销)。

我想过打开每个数据包N次,并以循环方式给每个线程一个句柄,但问题是它最终可能会得到(数据文件数)x(最大线程数) ) 打开句柄(很容易变成 8-16k),我必须在每次访问数据包时进行同步,或者使用一些无锁魔法来获取下一个空闲文件句柄。

因为这似乎不是一个原始问题(我猜,任何数据库引擎都有类似的问题,您可以有 M 个表(数据包)和 N 行(在我的情况下是文件),并且您希望允许尽可能多的线程同时读取行)。那么这里推荐的做法是什么?顺便说一句,它应该在 Windows 和 Linux 上运行,因此欢迎使用可移植的方法(或者至少可以在两个平台上工作的方法,即使它们使用不同的底层 API ——只要它们可以被包装,我很高兴)。

[EDIT] 这不是关于速度,而是关于隐藏延迟。也就是说,我可能每秒读取 100 个这些小文件,所以我最多只能达到 1 mib/s。我主要关心的是查找时间(因为我的访问模式是不可预测的),我想通过在向用户显示旧数据的同时触发读取来隐藏它们。问题是如何允许多个线程对多个文件发出 IO 请求,并且可能有 >1 个线程访问单个文件。

如果其中一个调用需要 70 毫秒左右才能完成,这确实没有问题,但如果读取调用阻塞,我负担不起。

【问题讨论】:

您的数据是否存储在 RAID 阵列或类似的东西上? 不,一点也不。同样,速度不是主要问题,只要它是异步运行的,即使每次读取需要 100 毫秒,我也可以。问题是如何允许多个线程同时访问一个文件:) 我看到内存映射整个文件集不是一个选项,但也许您可以使用一块内存作为最常访问的文件的大缓存(或者将它们全部只读一次?) 【参考方案1】:

我不认为多线程对磁盘读取有很大帮助。假设文件在一个磁盘盘片上,你只有一组读头可以访问它,所以你就在那里被序列化。

在这种情况下,我想我会有一个磁盘读取进程,将文件顺序读取到缓冲区中(这有望最大限度地提高读取性能,因为读取头不需要移动太多,假设数据文件相当完整) 和许多读取缓冲区的处理线程,当它们完成处理时将它们标记为空闲。

无论您选择如何继续,我是否建议您确保您的代码的结构能够轻松配置不同类型线程的数量,最好是从可执行文件命令行中配置。在这种情况下,您需要尝试不同的线程配置,以找到适合您特定情况的最佳数量。

【讨论】:

是的,但我不想在每次访问文件时都阻止。问题是,我可以在等待磁盘寻道时间的同时继续显示当前数据,并且我想尽可能减少停顿。 可能不止一个读取线程,以防卷是 RAID1。【参考方案2】:

Linux 根本没有可用的异步 IO(是的,有 aio_*,但它只适用于 O_DIRECT 并且有各种奇怪的限制)所以如果你想要一些可移植的东西,你只需要使用普通的阅读电话。 mmap 可以,但如果您每次只阅读少量内容,更改映射的成本可能会有点高。

现在,我不了解 Windows,但在 Linux 上,有一个 pread() 函数可以让您从给定偏移量的文件描述符中读取,而不会影响文件描述符的查找指针。有了这个,你可以让任意数量的线程从同一个文件中读取,而无需锁定文件描述符或类似的傻事。

【讨论】:

【参考方案3】:

我能想象到读取大量数据的最快方法是创建一个磁盘分区(主分区或逻辑分区,但没有 LVM),然后直接读取分区设备(例如/dev/sda5顺序,没有文件系统,每个磁盘只使用一个线程。顺序访问原始磁盘很重要,以避免磁盘寻道,这比顺序读取要慢得多。

【讨论】:

【参考方案4】:

会伤害你的问题是头部争用;不管你运行了多少线程,头部一次只能在一个位置。您可以选择将文件分布在多个磁盘上吗?

【讨论】:

可能,但同样,这与读取速度无关,而只是有效地隐藏延迟。【参考方案5】:

mmap 方法派上用场了。您不需要为每次读取执行 mmap/unmap 循环,而是有一个线程处理所有这些映射和处理 指针(实际上是偏移量和长度)。当线程访问映射到文件的虚拟内存时,操作系统会调度真正的读取部分。

请记住,过多的线程不会提高阅读速度。数据库引擎的 I/O 线程数量通常非常有限,可以满足应用程序线程的所有 I/O 需求。

【讨论】:

mmap 很好,但是文件的总大小太大而无法在 x86 上对它们进行 mmap。我可以打开一个文件句柄,并且每次都映射我需要读取的部分(假设两个线程可以同时mmap() 在同一个文件上),但我想知道这是否比重新打开文件更快并发出另一个read 您不需要一次映射整个文件。只需对您需要的部分进行 mmap,但只需执行一次 mmap 即可一次读取 100 条记录,因此您可以在 100 次读取时分摊 mmap/unmap 成本。顺便说一句,打开/关闭周期也很慢,读取通常涉及缓冲区复制操作。如果您的数据大部分看起来只能读取一次,您甚至可以考虑 O_STREAM 和/或 O_DIRECT 打开标志。

以上是关于从单个文件同时读取的主要内容,如果未能解决你的问题,请参考以下文章

ResourceBundle 同时从 2 个 Properties 文件中读取

从字符串 C++ 中读取单词,同时忽略空格、数字和符号。

如何在保持换行符的同时将 .txt 文件读入单个 Java 字符串?

在 C 或 C++ 中的同一个套接字上同时读取和写入

将 Python 3.3 嵌入到 C++ 程序中,同时只能从输入中一次读取一行

使用 Perl,如何在保持参照完整性的同时从单个表加载多个表?