我的目标是在OSX环境中的QT项目中使用C ++解析大型csv文件。 (当我说csv我的意思是tsv和其他变种1GB~5GB)。
这似乎是一项简单的任务,但是当文件大小变大时,事情会变得复杂。我不想编写自己的解析器,因为与解析csv文件有关的许多边缘情况。
我找到了各种csv处理库来处理这项工作,但是在我的机器上解析1GB文件需要大约90~120秒,这是不可接受的。我现在没有对数据做任何事情,我只是为了测试目的而处理和丢弃数据。
cccsvparser是我尝试过的图书馆之一。但唯一足够快的库fast-cpp-csv-parser给出了可接受的结果:我的机器上有15秒,但只有在知道文件结构时才有效。
使用示例:fast-cpp-csv-parser
#include "csv.h"
int main(){
io::CSVReader<3> in("ram.csv");
in.read_header(io::ignore_extra_column, "vendor", "size", "speed");
std::string vendor; int size; double speed;
while(in.read_row(vendor, size, speed)){
// do stuff with the data
}
}
正如您所见,我无法加载任意文件,我必须专门定义变量以匹配我的文件结构。我不知道任何允许我在运行时动态创建这些变量的方法。
我尝试的另一种方法是逐行读取csv文件fast-cpp-csv-parser LineReader类非常快(读取整个文件大约需要7秒),然后使用cccsvparser解析每一行可以处理字符串的lib。但这需要大约40秒才能完成,与第一次尝试相比这是一个改进,但仍然是不可接受的。
我已经看到了与csv文件解析相关的各种stackoverflow问题,它们都没有将大文件处理到帐户中。
此外,我花了很多时间用谷歌搜索来找到这个问题的解决方案,我真的很想念包裹经理喜欢 npm 或 pip 在搜索时提供的自由开箱即用的解决方案。
我将不胜感激任何有关如何处理此问题的建议。
修改
当使用@fbucek的方法时,处理时间减少到25秒,这是一个很大的改进。
我们可以对此进行更优化吗?
答案 0 :(得分:8)
我假设你只使用一个帖子。
多线程可以加速您的过程。
到目前为止,最佳成就是 40秒。让我们坚持下去。
我假设您首先阅读然后处理 - &gt; (大约7秒读取整个文件)
7秒供阅读 33秒进行处理
首先您可以将文件分成块,让我们说50MB。 这意味着您可以在读取50MB文件后开始处理。您无需等到整个文件完成。 读数为0.35秒(处理时为0.35 + 33秒= cca 34秒)
使用多线程时,可以一次处理多个块。从理论上讲,这可以加速核心数量。我们假设您有4个核心。 那是33/4 = 8.25秒。
我认为您可以使用4个核心加速处理,最多可达 9 s。。
查看QThreadPool和QRunnable或QtConcurrent 我更喜欢QThreadPool
将任务分成几部分:
看起来像这样
loopoverfile {
whenever chunk is ready {
ChunkProcessor *chunkprocessor = new ChunkProcessor(chunk);
QThreadPool::globalInstance()->start(chunkprocessor);
connect(chunkprocessor, SIGNAL(finished(std::shared_ptr<ProcessedData>)), this, SLOT(readingFinished(std::shared_ptr<ProcessedData>)));
}
}
您可以使用std :: share_ptr传递已处理的数据,以便不使用QMutex或其他内容,并避免多线程访问某些资源时出现序列化问题。
注意:为了使用自定义信号,您必须在使用前注册
qRegisterMetaType<std::shared_ptr<ProcessedData>>("std::shared_ptr<ProcessedData>");
编辑:(基于讨论,我的回答并不清楚) 使用什么磁盘或速度有多关系并不重要。读取是单线程操作。 建议使用此解决方案只是因为它需要7秒才能读取,并且无论它是什么磁盘都无关紧要。 7秒是最重要的。唯一的目的是尽快开始处理,而不是等到阅读结束。
您可以使用:
QByteArray data = file.readAll();
或者您可以使用主要想法:(我不知道为什么需要7秒才能阅读,背后是什么)
QFile file("in.txt");
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
return;
QByteArray* data = new QByteArray;
int count = 0;
while (!file.atEnd()) {
++count;
data->append(file.readLine());
if ( count > 10000 ) {
ChunkProcessor *chunkprocessor = new ChunkProcessor(data);
QThreadPool::globalInstance()->start(chunkprocessor);
connect(chunkprocessor, SIGNAL(finished(std::shared_ptr<ProcessedData>)), this, SLOT(readingFinished(std::shared_ptr<ProcessedData>)));
data = new QByteArray;
count = 0;
}
}
一个文件,一个线程,读取速度几乎和第34行一样快;没有&#34;中断。 您对数据的处理是另一个问题,但与I / O无关。它已经在记忆中了。 因此,只关注机器上的 5GB文件和RAM 。
这是非常简单的解决方案所有你需要的是子类QRunnable,重新实现运行函数,完成时发出信号,使用共享指针传递处理数据,并在主线程中将数据联合成一个结构或其他。简单的线程安全解决方案。
答案 1 :(得分:2)
我建议一个多线程建议,稍有不同的是一个线程专用于以预定义(可配置)大小的块读取文件,并继续向一组线程(多个基于cpu核心)提供数据。我们假设配置如下:
块大小= 50 MB
磁盘线程= 1
处理线程= 5
请注意,为了便于说明,需要进行偏移,需要以编程方式处理线分隔符映射的偏移。
答案 2 :(得分:0)
如果您使用的解析器未明确分发,则该方法不可扩展。
我会投票支持下面这样的技术
现在,已经说过了,为什么不能像框架一样使用Hadoop?