【问题标题】:Many small files or one big file? (Or, Overhead of opening and closing file handles) (C++)许多小文件还是一个大文件? (或者,打开和关闭文件句柄的开销)(C++)
【发布时间】:2009-07-29 05:15:29
【问题描述】:

我创建了一个执行以下操作的应用程序:

  1. 进行一些计算,写入计算数据到一个文件 - 重复 500,000 次(总共,一个接一个地写入 500,000 个文件) - 再重复 2 次(总共有 150 万个文件写的)。
  2. 读取文件中的数据,使用文件中的数据进行一些密集计算 - 重复 1,500,000 次迭代(迭代第 1 步中写入的所有文件。)
  3. 重复第 2 步,迭代 200 次。

每个文件约为 212k,所以总的来说我有 ~300Gb 的数据。在 2.8 Ghz 的 Core 2 Duo CPU 上,整个过程似乎需要大约 40 天。

我的问题是(您可能已经猜到了)是完成整个过程所需的时间。所有计算都是串行的(每个计算都依赖于之前的计算),所以我不能将此过程并行到不同的 CPU 或 PC。我正在尝试考虑如何使流程更高效,并且我很确定大部分开销都用于文件系统访问(duh ...)。每次访问文件时,我都会打开它的句柄,然后在完成读取数据后关闭它。

我改进运行时间的一个想法是使用一个 300Gb 的大文件(或几个 50Gb 的大文件),然后我将只使用一个打开的文件句柄并简单地查找每个相关数据并读取它,但我不是打开和关闭文件句柄的开销。有人可以对此有所了解吗?

我的另一个想法是尝试将文件分组为更大的 ~100Mb 文件,然后每次读取 100Mb 而不是多次读取 212k,但这比上面的想法实现起来要复杂得多。

无论如何,如果有人可以就此给我一些建议或知道如何改进运行时间,我将不胜感激!

谢谢。

分析器更新:

我在进程上运行了一个分析器,看起来计算需要 62% 的运行时间,而文件读取需要 34% 的时间。这意味着即使我奇迹般地将文件 i/o 成本降低了 34 倍,我仍然剩下 24 天,这是一个相当大的改进,但仍然很长:)

【问题讨论】:

  • 您是否考虑过将其存储在数据库中?
  • 我考虑过,但是这样会不会加快数据提取速度?
  • 你说你很确定文件的打开/关闭是一个瓶颈。这是基于分析程序的预感还是更多的一般预感?如果是后者,我会认真建议先分析您的代码。
  • 你们是对的,我将运行一些分析并更新问题。

标签: c++ optimization file-io


【解决方案1】:

打开文件句柄不太可能成为瓶颈;实际的磁盘 IO 是。如果您可以并行化磁盘访问(例如,通过使用多个磁盘、更快的磁盘、RAM 磁盘……),您可能会受益更多。另外,请确保 IO 不会阻塞应用程序:从磁盘读取,并在等待 IO 时进行处理。例如。带有一个阅读器和一个处理器线程。

另一件事:如果下一步取决于当前计算,为什么还要努力将其保存到磁盘?也许通过对流程依赖关系的另一种看法,您可以重新处理数据流并摆脱大量 IO。

哦,是的,并且测量它 :)

【讨论】:

  • 我只在第一步保存数据,在第二步使用它,我必须将数据保存到文件中。实际上,使用另一个线程从磁盘读取的建议听起来是个好主意。
  • “为什么要努力将它保存到磁盘?”他很可能没有 300GB 的 RAM。
  • @onebyone: 但是他他是靠上一步的结果来计算当前的,1步大约是212kB,所以……
  • 在第 2 步中,我需要第 1 步 (300Gb) 中的所有数据,并且需要 200 次,因此我无法即时计算第 1 步中的数据。
  • 我将时间缩短了一半,增加了线程支持,甚至使用 Intel 的 TBB 来最大限度地利用我的两个内核 :)
【解决方案2】:

每个文件约为 212k,所以我有 约 300Gb 的数据。它看起来像 整个过程大约需要 40 天......所有的 计算是串行的(每个 计算取决于一个 之前),所以我不能并行这个 处理到不同的 CPU 或 PC。 ... 漂亮的 确保大部分开销都用于 文件系统访问...每个 当我访问一个文件时我打开一个句柄 到它,然后在我完成后关闭它 读取数据。

连续写入 300GB 的数据可能需要 40 分钟,只是 40 天的一小部分。磁盘写入性能在这里应该不是问题。

您只打开一次文件的想法是正确的。可能在每次操作后关闭文件会导致处理阻塞,直到磁盘完全写出所有数据,从而抵消了磁盘缓存的好处。

我敢打赌,这个应用程序的最快实现将使用内存映射文件,所有现代操作系统都具有此功能。它也可能最终成为最简单的代码。您需要 64 位处理器和操作系统,您应该需要 300GB 的 RAM。一次将整个文件映射到地址空间,然后使用指针读取和写入数据。

【讨论】:

    【解决方案3】:

    在进行任何更改之前,运行探查器跟踪以找出大部分时间花费在哪里以确保您真正优化真正的问题可能会很有用。

    【讨论】:

      【解决方案4】:

      从您的简短解释看来,xtofl 建议线程是正确的方法。我建议您先分析您的应用程序,以确保时间在 IO 和 cpu 之间分配。

      然后我会考虑由两个队列连接的三个线程。

      1. 线程 1 读取文件并将它们加载到内存中,然后将数据/指针放入队列中。如果队列超过一定大小,则线程休眠,如果低于一定大小,则重新启动。
      2. 线程 2 从队列中读取数据并进行计算,然后将数据写入第二个队列
      3. 线程 3 读取第二个队列并将数据写入磁盘

      您可以考虑合并线程 1 和 3,这可能会减少磁盘争用,因为您的应用一次只能执行一次磁盘操作。

      操作系统如何处理所有文件?它们都在一个目录中吗?浏览目录(gui filemanager/dir/ls)时的性能如何?如果这种性能很差,您可能在文件系统的舒适区之外工作。尽管您只能在 unix 上更改此设置,但某些文件系统针对不同类型的文件使用进行了优化,例如大文件、大量小文件等。您也可以考虑将文件拆分到不同的目录。

      【讨论】:

        【解决方案5】:

        使用SQLite 怎么样?我认为您可以只使用一张桌子。

        【讨论】:

        • 我考虑过,但是这样会不会加快数据提取速度?
        • 更快意味着多少?我不认为它会更慢。考虑打开和关闭数千个文件或在单个大文件中搜索信息的开销。通过使用 SQLite,您可以获得单个文件,并通过索引进行了优化。此外,SQLite 支持内存数据库。这意味着,您可以缓存部分数据以便更快地访问,而无需为此编写新代码。
        • 我不需要索引,因为我会知道文件开头的确切偏移量。缓存可能会有所帮助,但这取决于缓存的实现。
        【解决方案6】:

        应该研究使用内存映射文件,因为它会减少系统调用的次数。

        【讨论】:

        • 就像使用一个映射到内存的大文件一样?
        • 不完全是,它可以用于越来越小的文件,但受到最大进程地址空间的限制(32 位大约 4GB)。您需要支持来自操作系统的内存映射文件。基本上,虚拟内存管理器负责将文件映射到进程地址空间。在 unix 中,这由 mmap 调用提供,在 windows 中由 createfilemapping 调用提供。
        • 一个 64 位操作系统应该能够在一个连续的地址范围内映射 300MB。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-10-01
        • 1970-01-01
        相关资源
        最近更新 更多