多线程文件阅读

时间:2016-12-21 10:17:50

标签: c++ performance openmp

我需要读取/解析一个大型二进制文件(4~6 GB),该文件以8192字节的固定块存在。我目前的解决方案涉及使用Single Producer Multiple Consumer(SPMC)模式流式传输文件块。

修改

文件大小= N * 8192字节

我需要做的就是对这8192个字节中的每个字节做一些事情。该文件只需要自上而下读取一次。

考虑到这应该是一个令人尴尬的并行问题,我希望 X 线程在相同的范围内读取(文件大小/ X )大小独立。线程根本不需要彼此通信。

我尝试生成 X 线程来打开同一个文件并寻求各自的部分进行处理,但是,这个解决方案似乎有问题由于HDD机械寻求并且显然比SPMC解决方案表现更差。

如果在SSD上使用此方法会有什么不同吗?

或者更简单地将内存映射到整个文件并使用#pragma omp parallel for来处理块?我想我需要足够的RAM才能做到这一点?

你会建议什么?

2 个答案:

答案 0 :(得分:5)

  

你会建议什么?

请勿使用mmap()

Linux Torvalds himself

  

人们喜欢用mmap()和其他方式来玩页表   优化复制操作,有时值得。

     

但是,使用虚拟内存映射玩游戏非常有用   本身很贵。它有许多非常不利的缺点   人们往往会忽视,因为记忆复制被认为是非常重要的东西   缓慢,有时优化该副本被视为一个显而易见的   改良效果。

     

下行到mmap:

     
      
  • 非常明显的设置和拆卸成本。我的意思是明显的。   它就像跟随页面表一样干净地取消映射所有内容。它是用于维护所有内容列表的簿记   映射。它是取消映射后需要的TLB刷新。
  •   
  • 页面错误是昂贵的。这就是映射如何填充,而且速度很慢。
  •   
     

mmap的好处:

     
      
  • 如果数据被一遍又一遍地重复使用(在单个地图操作中),或者如果你可以通过映射内容来避免很多其他逻辑,那么mmap()就是切片面包以来最好的东西。
  •   
     

这可能是你多次访问的文件(可执行文件的二进制图像在这里是明显的例子 - 代码在各处跳转),或者是一个设置它可以方便地映射的文件整个过程中不考虑mmap()刚刚获胜的实际使用模式。您可能拥有随机访问模式,并使用mmap()作为跟踪实际需要的数据的方式。

     
      
  • 如果数据很大,mmap()是让系统知道它可以用数据集做什么的好方法。内核可以忘记页面,因为内存压力迫使系统将页面输出,然后再自动重新获取它们。

         

    自动共享显然就是这种情况。

  •   
     

但是你的测试套件(只是复制一次数据)可能是悲观的   对于mmap()。

注意最后一次 - 仅使用一次数据对于mmap()来说是一个不好的用例。

对于SSD上的文件,因为没有物理头部搜索动作:

  1. 使用open()打开文件一次,获取单个int文件描述符。

  2. 每个线程使用pread()来读取适当的8kB块。 pread()在不使用lseek()的情况下从指定的偏移量读取,并且不会影响正在读取的文件的当前偏移量。

  3. 您可能需要比CPU核心更多的线程,因为在每个线程上都会有重要的IO等待。

    对于机械磁盘上​​的文件:

    您希望最小化机械磁盘上​​的磁头搜索。

    使用带有直接IO的open()打开文件一次(假设Linux,open( filename, O_RDONLY | O_DIRECT );)以绕过页面缓存(因为您要转发文件并且永远不会重新读取任何文件)它的一部分,页面缓存对你没有好处)

    1. 使用单个生产者线程,读取大块(比如说64k到1MB +) 进入N页对齐的缓冲区之一。
    2. 读取缓冲区时,将其传递给工作线程,然后读取以填充下一个缓冲区

    3. 当所有工作人员使用他们的部分缓冲区完成后,通过 缓冲回阅读线程。

    4. 您需要尝试使用正确的read()大小,工作线程数以及传递的缓冲区数量。较大的read()将更有效,但较大的缓冲区大小会使内存需求更大,并且使得从工作线程返回缓冲区的延迟更加难以预测。您希望尽可能少地创建数据副本,因此您希望工作线程直接在从文件读取的缓冲区上工作。

答案 1 :(得分:4)

即使每个8K块的处理很重要(缺少OCR处理),i / o也是瓶颈。除非可以安排文件的某些部分已经被先前的操作缓存....

如果要运行的系统可以专用于此问题:

  1. 获取文件大小(fstat
  2. 分配大小的缓冲区。
  3. 打开并将整个文件读入缓冲区。
  4. 弄清楚如何对每个线程的数据进行分区并分离线程。
  5. 算法的时间。
  6. 然后,使用异步读取修改它。请参阅man aio_readman 7 aio了解需要完成的工作。