【问题标题】:How to improve performance of file reading by multiple threads?如何提高多线程读取文件的性能?
【发布时间】:2009-09-24 07:22:26
【问题描述】:

我需要在 Linux 下使用多个线程读取单个文件。 只有读操作,不需要写。 文件读取不需要每次都读取整个文件。 它每次都需要读取文件的一个或多个部分。 我预先存储了每个部分的偏移量。 文件太大,无法放入主内存。

例如,许多用户想要阅读这样的文件。 我使用线程或进程来读取文件以回答用户请求。 Linux下会发生什么? 所有的读取操作都会排队吗? 而操作系统会一一完成文件的读取? 是否可以提高此类操作的性能?

我正在尝试实现一个用于信息检索的简单倒排索引。 我将字典放入内存并在文件中发布列表。 每个文件都包含一个索引段。 在字典中,我可以存储诸如偏移量之类的东西来指向单词的发布列表的位置。 当 100 个用户想要在一秒钟内搜索某物时,他们会提交不同的查询。 所以每次读取都会读取文件的不同部分。

【问题讨论】:

  • 我问的时候说错了。文件读取不需要每次都读取整个文件。它每次都需要读取文件的一个或多个部分。我预先存储了每个部分的偏移量。

标签: c linux unix


【解决方案1】:

尝试以最简单的方式开始实现它 - 让操作系统通过缓存等方式使其高效。看看性能如何 - 它可能根本不会成为瓶颈。操作系统通常擅长这种事情:)

假设您能够多次打开文件以进行共享读取,我希望它可以正常工作,而所有读取操作都不会排队。

【讨论】:

    【解决方案2】:

    您的文件有多大,无法全部放入内存?

    最有效的方法是踢到 o/s,并使用mmap() 将文件映射到(虚拟)内存中,然后让线程全部通过内存访问文件。如果您使用的是 32 位机器,这会将您的文件大小限制为“小于 4GB,但可能远远超过 2GB”;如果您使用的是 64 位计算机,则除了磁盘空间之外,您并没有真正的限制。

    请注意,文件不必全部在物理内存中mmap();但是,这一切都会合乎逻辑。

    【讨论】:

    • 一个文件可以是2GB,但是如果有10个这样的文件呢?例如,只剩下 2G 的可用物理内存。
    • 在 32 位机器上,你被斯诺克了。在 64 位机器上,操作系统负责文件的分页 - 线程正在使用的位将在内存中,而未使用的位将在磁盘上。
    【解决方案3】:

    线程都可以安全地独立读取文件,是的。最终,读取操作将在操作系统级别排队,因此驱动程序将读取请求序列化到磁盘。根据访问策略(即读取缓冲区大小),读取应该是交错的。除非您尝试在一个请求中读取整个文件(您不应该这样做,因为您说它太大而无法放入内存),否则读取请求将按照线程请求它们的大致顺序进行服务。 (我大概说一下,因为磁盘驱动程序可以重新排序它在队列中知道的读取请求以优化磁盘访问)。所以你描述的应该可以正常工作。并且操作系统会尽可能多地缓存读取(和预加载)。

    至于提高性能,取决于使用的数据和算法,有很多可能性。每个线程真的有必要读取整个文件来服务每个请求吗?为什么一遍又一遍地读取相同的数据?你不能集中一些信息以便线程可以共享读取的数据吗?这听起来像是一个昂贵的解决方案。而且,如果您一遍又一遍地重复读取大于 RAM 的文件,那么最近缓存的块很有可能被重新读取,可能会被挤出缓存。也许文件的索引可以为您节省一些读取时间,并且您可以根据索引缓存访问?还可以考虑使用mmap() 将文件映射到内存中,然后操作系统将在线程从不同块读取时将块分页进出。因此,值得重新考虑如何访问数据,以及您需要什么以及何时访问。如果您在此处发布更多信息,人们可能会提供更具体的建议。

    请记住,最有效的操作是您不执行的操作!

    【讨论】:

    • 我问的时候说错了。文件读取不需要每次都读取整个文件。它每次都需要读取文件的一个或多个部分。我预先存储了每个部分的偏移量。
    • 在这种情况下,您可以有一个类负责读取,并让线程请求块。它可以维护一个偏移索引,并缓存最近读取的块。但首先,值得进行一些分析以查看您需要优化的地方。即使是简单的strace 也可以让您了解读取模式。
    【解决方案4】:

    操作系统通常非常擅长优化对文件的访问(Linux 以积极缓存着称。)但我认为减少读取量对于提高效率至关重要,你真的无法摆脱单一的共享数据结构吗?代表文件的一部分?这样,单个线程读取,并且每个其他线程都从读取中受益。因为它只是读取,所以不应该对数据结构有任何争用,只有在填充时。如果每个线程每次都读取文件的不同部分,这当然是不可行的。

    鉴于您既不能从缓存中受益(很多),也不能共享文件的读取部分,没有什么可做的(只是读取文件),但要改进您的磁盘子系统:获得具有大量吞吐量的快速磁盘(RAID 10)。如果这还不够,请在不同的逻辑驱动器上制作两个或更多的文件副本,以进一步提高吞吐量。

    【讨论】:

    • 我正在尝试实现一个用于信息检索的简单倒排索引。我将字典放入内存并在文件中发布列表。每个文件都包含索引的分段。在字典中,我可以存储诸如偏移量之类的东西来指向单词的发布列表的位置。当 100 个用户想要搜索某些内容时,他们会提交不同的查询。所以每次读取都会读取文件的不同部分。您提到了缓存,但是如果字典使用大部分内存,Linux 将如何缓存文件,前提是文件很大。
    【解决方案5】:

    如果文件太大而无法装入系统内存,并且您有很多线程需要读取整个文件,那么您的应用程序很有可能会受到磁盘 I/O 的限制......无论您如何阅读文件,无论操作系统多么智能。

    如果这是不可接受的,那么您需要为您的应用程序提出一个替代架构。例如,您可以将文件转换为不同的形式,允许线程在不读取整个文件的情况下获取它们需要的信息。或者您可以将应用程序变成在不同机器上运行的独立进程,每个进程都有自己的文件副本。第三种可能性是添加一个线程,其唯一目的是读取和缓冲文件,并让现有线程从缓冲区中读取。 (通过让工作线程都在文件的同一区域上工作,您可以避免操作系统必须从磁盘多次读取文件的一部分。如果应用程序确实是磁盘绑定的,这可能会加快速度。)

    然而,这一切都是猜想。如果没有关于应用程序及其处理的文件的更多信息,很难给出体面的建议。

    编辑:根据您的后续 cmets,似乎线程毕竟不需要访问所有文件。我的第一个建议没有实际意义(你们都准备好了!),我的第三个建议也无济于事。我建议您按照@Jon Skeet 所说的那样做,并以简单的方式实现该系统。然后,如果存在性能问题,请寻找使其更快/更好的方法。例如:

    • 考虑实现最近查询及其结果的内存缓存。
    • 考虑使用多台机器,并按关键字范围对索引文件进行分区,以便每个部分都适合一台机器上的内存。
    • 如果您支持复杂查询,请考虑将它们拆分为简单查询并将简单查询发送到不同的机器(例如基于关键字分区),然后合并结果集。
    • 当用户只想查看前几个结果时,考虑避免计算大量结果集的方法。

    几年前,我从一位同事那里借了一本有趣的索引教科书。我想是Managing Gigabytes by Witten et al

    【讨论】:

      【解决方案6】:

      注意事项

      • 有单驱动,带单驱动
      • 并且有来自多个线程的多个(随机)访问

      在这种情况下,因为在你的多线程之下,事情是串行的(来自驱动层)......所以,你能做的最好的事情,

      • 提高进程的优先级(如果可能),这样其他进程就不会占用 CPU 时间
      • 在线程之间分配公平级别的调度
      • 基于访问的随机性(可以启用或禁用缓存)
        • 例如,您可以禁用缓存,如果读取是完全随机的并且您发现大多数时候都存在缓存未命中

      【讨论】:

        猜你喜欢
        • 2011-02-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-12-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多