在一个巨大的文件(C++)中进行许多小型盲写的最快方法?

Tre*_*son 5 c++ performance file-io

我有一些非常大(> 4 GB)的文件,包含(数百万)固定长度的二进制记录.我希望(有效地)将它们连接到其他文件中的记录,方法是将指针(即64位记录号)写入特定偏移的记录中.

详细说明,我有一对(键,记录号)元组列表,按键排序,我想对给定的一对文件执行每个连接,例如A和B.迭代列表对并匹配键产生表示连接记录的(键,记录号A,记录号B)元组的列表(为简单起见假设1:1映射).为了完成连接,我在概念上需要寻找列表中的每个A记录并在适当的偏移处写入相应的B记录号,反之亦然.我的问题是实际执行此操作的最快方法是什么?

由于连接记录列表按键排序,因此相关记录号基本上是随机的.假设文件比操作系统磁盘缓存大得多,那么做一堆随机搜索和写入似乎效率极低.我已经尝试通过将A-> B和B-> A映射放在稀疏数组中来对记录数进行部分排序,并在每次内存不足时将最密集的条目集群刷新到磁盘.这有利于大大增加在更新其第一个指针后为集群缓存适当记录的机会.然而,即使在这一点上,通常更好的做一堆搜索和盲写,或者手动读取文件的块,更新适当的指针,并把块写回来?虽然前一种方法更简单,并且可以通过操作系统进行优化,以便进行最小的扇区读取(因为它知道扇区大小)和副本(它可以通过直接读入正确对齐的缓冲区来避免复制),但它似乎是将导致极高的系统调用开销.

虽然我喜欢便携式解决方案(即使它涉及对广泛使用的库的依赖,例如Boost),但现代Windows和Linux是唯一的必备软件,因此我可以使用特定于操作系统的API(例如CreateFile)提示或分散/收集I/O).然而,这可能涉及很多工作甚至尝试,所以我想知道是否有人可以告诉我,它是否值得努力.

Dum*_*001 3

我尝试通过将 A->B 和 B->A 映射放入稀疏数组中来对记录号进行部分排序,并在内存不足时将最密集的条目簇刷新到磁盘。看来它会产生极高的系统调用开销。

您可以使用对文件的内存映射访问来避免系统调用开销。*NIX 上为mmap()Windows 上为 CreateFileMapping()

将文件逻辑分割成块,例如 32MB。如果块中需要更改某些内容,则 mmap() 它,修改数据,如果需要,可选 msync(),munmap(),然后移动到下一个块。

那是我首先尝试过的事情。操作系统会自动读取需要读取的任何内容(在第一次访问数据时),并且它会按照自己喜欢的方式对 IO 进行排队。

需要记住的重要一点是,真正的 IO 并不那么快。随机访问的性能限制因素包括 (1) 存储可以处理的每秒 IO 数 (IOPS) 和 (2) 磁盘寻道次数。(通常的 IOPS 在数百范围内。通常的寻道延迟为 3-5 毫秒。)例如,存储可以读/写 50MB/s:一秒内连续一个 50MB 的块。但如果您尝试按字节修补 50MB 文件,那么寻道时间只会降低性能。在一定限度内,即使只更新几个字节,也可以读取更多和写入更多。

另一个需要观察的限制是操作系统 IO 操作的最大大小:它取决于存储,但大多数操作系统会分割大于 128K 的 IO 任务。该限制可以更改,并且最好与存储中的类似限制同步。

还要记住存储。许多人忘记了存储通常只是一个。我在这里想说的是,启动线程的垃圾负载对 IO 没有帮助,除非你有多个存储。即使单个 CPU/核心也能够轻松使 RAID10 饱和,其 800 读取 IOPS 和 400 写入 IOPS 限制。(但是每个存储有一个专用线程至少在理论上是有意义的。)

希望有帮助。这里的其他人经常提到 Boost.Asio,我对此没有任何经验 - 但它值得检查。

PS 坦白说,我很想听到对你的问题的其他(更信息丰富的)答复。我已经上过几次船了,但一直没有机会真正深入其中。欢迎与 IO 优化(无论平台如何)相关的书籍/链接/等;)