在C++中读取大型CSV文件(〜4GB)

问题描述:

我想读取大型CSV文件并将其存储到地图中。我从阅读文件开始,看看需要多长时间来处理。这是我的循环:在C++中读取大型CSV文件(〜4GB)

while(!gFile.eof()){ 
    gFile >> data; 
} 

它需要我〜35分钟来处理包含3500万行和6列的csv文件。有什么办法可以加快速度吗?对于SO来说很新,所以如果不能正确地问道歉,

+1

我很肯定有一些图书馆明确为此目的。也就是说,35分钟听起来有点过长。你正在执行多少其他处理?确保你启用了优化! – tambre

+0

如果你一遍又一遍地重复写同一个变量,目前还不清楚为什么需要这么长时间。复制文件需要多长时间? – tadman

+0

[将整个ASCII文件读入C++ std :: string]可能重复(https://stackoverflow.com/questions/2602013/read-whole-ascii-file-into-c-stdstring) – smac89

背景
文件是流设备或概念。读取文件的最有效的用法是保持数据流(流动)。对于每个事务都有一个开销。数据传输越大,开销的影响就越小。所以,目标是保持数据流动。

内存比文件访问速度更快
搜索内存比搜索一个文件快许多倍。因此,搜索“单词”或分隔符将比逐字符地查找文件字符以查找分隔符更快。

方法1:通过线
使用std::getline线比使用operator>>快得多。尽管输入代码可能会读取一块数据;您只执行一个事务来读取一个记录而不是每列一个事务。请记住,保持数据流动和搜索内存的速度更快。

方法2:块读
在保持流动的流的精神,读存储器块到一个缓冲器(大缓冲液)。处理来自缓冲区的数据。这比逐行读取更有效,因为您可以使用一个事务读取多行数据,从而减少事务开销。

一个需要注意的是,你可能有一个记录交叉缓冲区的边界,所以你需要想出一个算法来处理这个问题。执行惩罚很小,并且每个事务只发生一次(考虑事务的这部分开销)。

方法3:多个线程
在保持数据流的精神,你可以创建多个线程。一个线程负责或将数据读入缓冲区,而另一个线程处理来自缓冲区的数据。保持数据流动,这项技术会有更好的运气。

方法4:双缓冲&多个线程
这需要上述方法3和增加了多个缓冲器。读线程可以填充一个缓冲区,然后开始填充第二个缓冲区。数据处理线程将在处理数据之前等待第一个缓冲区填满。该技术用于更好地将读取数据的速度与处理数据的速度相匹配。

方法5:内存映射文件
随着内存映射文件,操作系统处理文件的需求读入内存。您必须编写的代码越少,但是您对文件读入内存时的控制权也不尽如人意。这比逐场阅读还要快。

+0

Id避免多个线程,除非Im计算绑定,这往往是一堆额外的不必要的复杂性。Win32'CreateFile' +'ReadFile'或者nix'open' +'read'实际上并不难,并且可以以全磁盘速度读取(甚至比内存映射更好,因为您可以依次告诉操作系统您的读取),somthing C++甚至没有“专用”线程。对于简单的CSV等,甚至可以保持SSD的潜在读取速度而无需线程(每秒几百MB)。 –

+1

Downvoters:请添加评论解释你downvote。我亲自实施了这些技术,并在处理大小超过1GB的数据文件时发现了显着的性能提升。 –

让我们从瓶颈开始吧。

  1. 从磁盘读取
  2. 解码数据
  3. 商店在地图
  4. 显存速度
  5. 的内存量从盘面看

  • 读,直到你下降,如果你不能够快速读取磁盘上所有的带宽,你可以加快速度。忽略所有其他步骤,只能阅读。
  • 开始通过增加缓存到您的河道内
  • 组提示阅读
  • 使用mmap
  • 4GB是一个平凡的大小,如果你不这样做已经32 GB升级
  • 太慢买M.2磁盘。
  • 还在慢慢变得更加奇特,更换磁盘驱动,转储操作系统。镜像磁盘,只有你$£€是限制。

解码数据

  • 如果数据是在哪里都是相同的长度,那么所有解码可以并行地完成线,仅由存储器带宽的限制。
  • 如果行长度只有小心,可以并行完成行的查找结束,然后进行并行解码。
  • 如果行的顺序对于最终映射无关紧要,只需将文件拆分为#hardwarethreads部分,并让每个进程处理它们的部分,直到下一个线程部分的第一个换行符。
  • 内存带宽很可能会在CPU反正用完之前达到很远。在地图

    商店希望你已经事先想过这个地图是没有性病地图都是线程安全的。

  • 如果您不关心订单,则可以使用std :: array,并且您可以在全内存带宽下运行。
  • 可以说你想要使用std :: unordered_map,有一个问题就是它需要在每次写入后更新大小,所以实际上你的写操作只限于1个线程。
  • 您可以一次使用1个线程进行更新,而另一个预先计算记录的散列。具有一个线程写入的
  • 具有几乎每个写入都会成为高速缓存未命中严重限制速度的问题。
  • 所以如果速度不够快,请滚动您自己的hash_map,而不必在每次写入时都更新一个大小。
  • 为了确保线程安全,您还需要保护写操作,使用一个互斥锁可以使您比单个写入器慢或慢。
  • 你可以尝试锁定并等待免费...如果你不是专家,你会得到严重的头痛。
  • 如果您为散列选择了存储桶设计,那么您可以使X倍数量的写入器线程互斥量,使用散列值来选择互斥量。额外的互斥量增加了两个线程不会相互碰撞的可能性。

内存速度

  • 每条线将在存储器总线从磁盘中传送的至少4倍,一旦到RAM(至少一次,如果驾驶员是不好),一旦当数据被解码,一次当地图发出读取请求时,以及另一次地图写入时。
  • 如果驱动程序写入缓存,并且因此解码不会导致LLC未命中,一个好的设置可以节省多一次内存访问。

的内存

  • 金额,你应该有足够的内存来存放总文件,数据结构和一些中间数据。
  • 检查RAM是否比编程时间便宜。