如何通过多线程提高文件读取性能?

4
我需要在Linux下使用多线程读取单个文件。操作仅涉及读取,不需要写入。每次不需要读取整个文件,只需要读取一个或多个文件的部分内容。我预先存储了每个部分的偏移量。由于文件太大而无法放入主内存中,因此当许多用户想要读取这样的文件时,我使用线程或进程读取文件以回答用户请求。
在Linux下,所有读取操作都会排队等待完成,然后按顺序完成文件读取。为了提高性能,可以使用一些技术来优化。例如,可以使用异步IO或者mmap技术对文件进行映射。这些技术可以减少IO操作的时间,提高读取效率。
我正在尝试实现信息检索中使用的简单倒排索引。我将词典放在内存中,将Posting列表放在文件中。每个文件包含索引的一个片段。在词典中,我可以存储类似于偏移量的东西,指向单词的Posting列表的位置。当100个用户在一秒钟内想要搜索某些内容时,他们提交不同的查询。因此,每次读取都将读取文件的不同部分。

当我提问时,我说错了一些话。文件读取不需要每次读取整个文件。它需要每次读取一个或多个文件部分。我预先存储了每个部分的偏移量。 - Stephen Hsu
6个回答

3
尝试以最简单的方式实现它,让操作系统通过缓存等方式处理效率。查看性能表现 - 它可能根本不会成为瓶颈。操作系统通常擅长这种事情 :) 假设您能够多次打开文件进行共享读取,我预计它可以正常工作,而无需等待所有读取操作。

2
线程可以独立安全地读取文件。最终,读取操作将在操作系统级别排队,因此驱动程序会对磁盘进行串行化读取请求。根据访问策略(即读取缓冲区大小),读取应该是交错的。除非您尝试在一个请求中读取整个文件(这是不应该的,因为您说它太大而无法放入内存中),否则读取请求将以大约线程请求它们的顺序服务。(我说大约是因为磁盘驱动程序可以重新排序它知道的队列中的读取请求以优化磁盘访问)。所以您描述的应该可以正常工作。操作系统会相当积极地缓存读取(和预加载)尽可能多的内容。
至于提高性能,具体取决于数据和使用的算法。每个线程是否需要读取整个文件来处理每个请求?为什么要反复读取相同的数据?不能将某些信息集中起来,以便线程可以共享读取的数据?这听起来是一种昂贵的解决方案。如果您一遍又一遍地重复读取大于RAM的文件,则最近被缓存的块有很大机会被推出缓存。也许文件的索引可以节省您一些读取时间,并且您可以基于索引缓存访问?还可以考虑使用mmap()将文件映射到内存中,然后操作系统会根据线程从不同块读取的情况进行分页。因此,值得重新考虑如何访问数据,以及您需要什么和何时需要它。如果在此处发布更多信息,人们可能能够提供更具体的建议。
请记住,最有效的操作是您不执行的操作!

当我提问时,我说错了一些话。文件读取不需要每次都读取整个文件。它需要每次读取一个或多个文件部分。我事先存储了每个部分的偏移量。 - Stephen Hsu
在这种情况下,您可以有一个单独的类负责读取,并让线程请求块。它可以维护偏移量的索引,并缓存最近读取的块。但首先,值得进行一些分析以确定您需要优化的位置。即使是简单的“strace”也可以让您了解读取模式。 - gavinb

2
你的文件有多大,无法全部放入内存中吗?
最高效的方法是使用操作系统提供的mmap()将文件映射到(虚拟)内存中,让所有线程通过内存访问文件。如果你使用的是32位机器,那么文件大小限制在4GB以下,但可能会超过2GB;如果你使用的是64位机器,你实际上受硬盘空间的限制。
需要注意的是,使用mmap()时,文件不一定都在物理内存中,但它们在逻辑上都在那里。

一个文件可以达到2GB,但如果有10个这样的文件呢?例如,只剩下2G的可用物理内存。 - Stephen Hsu
在32位机器上,你会遇到麻烦。在64位机器上,操作系统会负责文件的分页 - 线程使用的位将存储在内存中,而未使用的位将存储在磁盘上。 - Jonathan Leffler

1
操作系统通常很擅长优化文件访问(Linux以其积极缓存而闻名)。但我认为减少读取量对提高效率至关重要,你真的不能仅使用一个共享数据结构来表示文件的一部分吗?这样,单个线程进行读取,其他每个线程都能从中受益。由于只是读取,因此数据结构上不应该有任何争用,仅在它被填充时可能存在争用。如果每个线程每次都读取文件的不同部分,则当然无法实现这一点。

鉴于您既不能从缓存中获得(太多)的好处,也不能共享文件的读取部分,那么除了改善磁盘子系统外,就没有太多可以做的了(只需读取文件):获取具有大量吞吐量的快速磁盘(RAID 10)。如果这还不够,则将文件复制到不同逻辑驱动器上两个或更多副本,以便能够进一步增加吞吐量。


我正在尝试实现一个用于信息检索的简单倒排索引。我将字典放在内存中,将Posting列表放在文件中。 每个文件包含索引的分段。在字典中,我可以存储类似于偏移量的东西,以指向单词的Posting列表的位置。当100个用户想要搜索某些内容时,他们会提交不同的查询。因此,每次读取都会读取文件的不同部分。您提到了缓存,但如果字典使用大量内存,Linux如何缓存文件,特别是文件很大的情况下。 - Stephen Hsu

0

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

如果这是不可接受的,那么您需要为应用程序设计替代架构。例如,您可以将文件转换为不同的形式,使线程能够获取所需信息而无需读取整个文件。或者您可以将应用程序转换为在单独的机器上运行的单独进程,每个进程都有自己的文件副本。第三种可能性是添加一个线程,其唯一目的是读取和缓冲文件,并使现有线程从缓冲区中读取。(通过让工作线程都在文件的同一区域工作,您可以避免操作系统多次从磁盘读取文件的部分。如果应用程序真正受到磁盘限制,这可能会加速它。)

然而,所有这些都是推测。没有更多关于应用程序和处理它的文件的信息,很难给出合理的建议。

编辑:根据您的后续评论,似乎线程不需要访问所有文件。我的第一个建议无效(您已经在执行它了!),我的第三个建议也不会有所帮助。我建议您按照@Jon Skeet的建议简单实现系统。然后,如果出现性能问题,请查找使其更快/更好的方法。例如:

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

几年前,我从同事那里借阅了一本有趣的索引教材。我认为是Witten等人的Managing Gigabytes


0

需要注意的要点

  • 只有一个驱动器,单个驱动器
  • 多个线程可以进行随机访问

在这种情况下,由于多个线程在下面,所以从驱动程序层开始是串行的。因此,您可以:

  • 提高您的进程优先级(如果可能),以便其他进程不会占用CPU时间
  • 在线程之间分配公平的级别调度
  • 根据访问的随机性(您可以启用或禁用缓存)
    • 例如,如果读取完全是随机的并且大部分时间都没有缓存命中,则可以禁用缓存

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接