在 C/C++ 中快速读取多个文件的某些字节

Posted

技术标签:

【中文标题】在 C/C++ 中快速读取多个文件的某些字节【英文标题】:Fast read of certain bytes of multiple files in C/C++ 【发布时间】:2010-05-20 14:20:13 【问题描述】:

我一直在网上搜索这个问题,虽然有很多关于 C/C++ 读/写的类似问题,但我还没有找到关于这个特定任务的信息。

我希望能够从多个文件(256x256 文件)仅读取位于每个文件特定位置的sizeof(double) 字节。现在我的解决方案是,对于每个文件:

    打开文件(读取,二进制模式):

    fstream fTest("current_file", ios_base::out | ios_base::binary);

    求我想读的位置:

    fTest.seekg(position*sizeof(test_value), ios_base::beg);

    读取字节:

    fTest.read((char *) &(output[i][j]), sizeof(test_value));

    并关闭文件:

    fTest.close();

这大约需要 350 msfor for 结构中运行,迭代次数为 256x256(每个文件一个)。


问:你认为有没有更好的方法来实现这个操作?你会怎么做?

【问题讨论】:

如果我理解正确,您有 65536 个文件要读取,但您从哪里获得文件名?目录列表?预填充数组? API 调用?还有,你能不能把“外部”结构合并修改成不需要读取65536个文件? 我知道性能越多越好,但 350ms 真的有问题吗?至少您当前的解决方案是可移植的。为了优化,您可能需要查看是否存在依赖于操作系统的技巧或其他数据组织。 @roygbiv:现在它在所有迭代中使用相同的文件(这只是一个预测试)。另一方面,我不确定我是否能够优化流程以停止调用 65536 文件。 @stefaanv:错误,如果您只需要运行一次代码,350 毫秒就足够了。但我做这个操作 256x256 次,这意味着我有 14 个小时的生命等待它结束:( 我无法理解的是为什么您只需要 65536 个文件中的一个 8 字节值。无法以其他格式获取此数据? 如果您的 65536 个文件都在一个目录中,那可能会大大降低速度。尝试使用树形结构,使文件W_i_j 存储在i/W_i_j 中。 【参考方案1】:

如果可能,我建议重新组织数据。例如,将所有这些双打放在一个文件中,而不是将它们分散到多个文件中。

如果您需要多次运行程序并且数据不变,您可能需要创建一个工具来首先优化数据。

文件的性能问题是:

    开销提升硬盘驱动器。 overhead定位文件。 在文件中定位。 读取数据。 关闭文件对 表演。

在大多数使用大量数据的基于文件的系统中,读取数据经过优化,其持续时间比任何开销都长。请求将被缓存和排序以获得最佳磁盘访问。不幸的是,在您的情况下,您没有读取足够的数据,因此开销现在比读取的持续时间更长。

我建议尝试对数据的读取操作进行排队。占用 4 个线程,每个线程打开一个文件并读取双精度数,然后将它们放入缓冲区。这里的想法是错开操作。

线程 1 打开一个文件。 线程 2 打开文件,而线程 1 正在定位。 线程 3 打开文件,而线程 2 是定位,线程 1 是 读取数据。 线程 4 打开一个文件,线程 3 位置,线程 2 读取,线程 1 关闭。

希望这些线程可以保持硬盘驱动器足够繁忙而不会变慢;持续的活动。您也许可以先在单个线程中尝试此操作。如果您需要更好的性能,您可能需要考虑将命令直接发送到磁盘驱动器(先订购它们)。

【讨论】:

【参考方案2】:

也许线程会有所帮助。

但首先你可以尝试一些更简单的方法。制作程序的两份副本,一份读取前 32768 个文件,另一份读取后半部分。同时运行这两个程序。这需要不到 14 小时吗?

如果没有,那么添加线程可能是无用的。正如上面 roygiv 建议的那样,碎片整理可能会有所帮助。

添加:14 小时显然是错误的,因为这几乎是每个文件 1 秒。 Alejandro 上面的评论说,使用固态驱动器,每个文件的时间只有 0.1 毫秒,总共 6.5 秒。这对我来说似乎很快。

所以我猜 Alejandro 必须重复此操作大约 7000 次,每次使用来自 65536 个文件的不同数据片段。如果是这样,还有两个建议是:

编写一个程序将文件分类到一个 新文件。你可能有足够的 SSD 上的空间来执行此操作,因为您的 其他 SO 问题表明 32 GB 数据,而 SSD 可能是几个 次。然后每次运行只使用 这个巨大的文件,它删除 65535 打开和关闭。

而且,不仅仅是串联, 在创建大文件时 可以“反转行和列” 或“条带化数据”,提供 地点。

进一步补充:您可能已经考虑过这一点,您的短语“将读取的数据写入单个文件”。

【讨论】:

如果您的文件存储在单个驱动器上,您的 I/O 子系统可能会饱和。但是,如果它们位于不同的驱动器上,您可能会看到性能略有提高。 我不会使用 for 循环 - 我会使用队列,然后让不同的线程从队列中拉出文件进行处理。 @roygbiv 是正确的,但是如果您使用本地驱动器并且如果您在网络上运行,那么您的 I/O 将会饱和,那么您的网络连接将会饱和。如果不进一步了解您的系统的方式和原因,我们将无法提供更多帮助。 如果可能的话。运行此测试,其中一半文件位于一个物理硬盘驱动器上,另一半位于另一个物理硬盘驱动器上。 你是对的,我错了 14 小时。在回家的路上,我正在考虑这个问题,将文件连接成一个文件的想法是我的建议之一,现在我看到你同意了!这可能会恢复读取中的 350 毫秒(考虑到文件大小可能会改变查找时间)。【参考方案3】:

如果你真的想优化它,你可能想放弃 C++ fstream 的东西,或者至少关闭它的缓冲。 fstream 做了很多内存分配和释放,缓冲可能会读取比需要更多的数据。操作系统可能需要读取整个页面才能获得所需的几个字节,但 fstream 可能希望它至少将那么多(可能更多,需要更多读取)复制到其缓冲区,这需要时间。

现在,我们可以继续取得更大的胜利。您可能想直接使用操作系统的 IO 例程。如果您使用的是 POSIX 系统(如 Linux),那么 openlseekreadclose 是一个很好的首选,如果您没有下一个系统调用,则可能需要.

如果您尝试读取的所有文件都位于一个目录(文件夹)或一个目录下,那么您可能会发现使用opendiropen("directory_name", O_DIRECTORY) 打开目录(取决于您是否需要阅读自己的目录条目)然后调用openat,它将目录条目文件描述符作为其参数之一将加快打开每个文件的速度,因为操作系统不会努力查找您尝试打开的文件每次(该数据可能会在操作系统的文件系统缓存中,但仍然需要时间并且需要进行大量测试)。

然后您可以使用pread 系统调用读取您的数据,而无需对您需要的数据的位置进行任何查找。 pread 采用偏移量,而不是使用操作系统的当前搜索点概念。这将至少为您节省一个系统调用。

编辑

如果您的系统支持异步 IO,这应该会加快速度,因为您可以继续并在检索之前让操作系统知道您想要什么(这可以让操作系统更好地安排磁盘读取,尤其是对于旋转磁盘),但这可能会变得复杂。不过,它可能会为您节省大量时间。

【讨论】:

那么,使用 openlseekreadclose 而不是 C++ fstream 函数真的相关吗?就我个人而言,我觉得纯 C 更舒服,但我被推荐使用 C++ 来达到这个目的。我会做一些测试。 如果您想从中获得最高性能,可以。来自 C++ 的流非常复杂,并且在幕后做了很多事情。你做了很多次非常具体的事情。如果您在带有strace 的系统上运行它,请尝试用它运行它进行几次迭代,看看它会吐出什么。您也可以对 ltrace 执行相同的操作,并查看在您打开、查找、读取和关闭流时,mallocfree 被调用了多少次。【参考方案4】:

鉴于问题的性质,我不确定您可以从中挤出多少性能。如果文件分布在几个不同的磁盘上,那么我可以看到为每个磁盘创建一个线程;这样你就可以一次并行化多个读取。但是,如果它们都在同一个磁盘上,那么在某种程度上,所有读取都将被序列化(我认为;我不是存储专家)。

I/O 是您的限制因素,而不是算法。

【讨论】:

【参考方案5】:

默认情况下 fstream API 不包括缓冲吗?我想知道是否将 API 切换到不使用缓冲的 API 或使用 setvbuf 禁用缓冲可能会加快速度。底层操作系统的缓存操作很可能意味着没有区别,但知道会很有趣。

【讨论】:

【参考方案6】:

颠倒迭代顺序。或者至少从磁盘读取一整页数据(例如每个文件 4kB)并将其存储在内存中,直到下一次通过。然后,您只需要在每 512 次通过时实际触摸文件系统。它将花费 256MB 的 RAM,但会节省数百 GB 的文件 I/O(即使您只请求 8 个字节,磁盘也必须将整个页面传输到缓存中)。并且你的操作系统的磁盘缓存替换算法很可能会删除 65k 调用打开旧的文件,所以不要相信它会为你做优化。

【讨论】:

以上是关于在 C/C++ 中快速读取多个文件的某些字节的主要内容,如果未能解决你的问题,请参考以下文章

C/C++ 服务器,通过标准输入/标准输出与客户端通信(阻塞标准输入,直到读取了多个字节)

读取 uart 十六进制值会忽略某些字节/字符

读取 JPG 文件,直到出现某些字节

字节输入流读取字节数据和字节输入流一次读取多个字节

Go-文件目录操作分类详解(创建打开关闭读取写入判断等)

java 字节流 多个文件写入一个文件 为啥前面写入的内容会被覆盖了