大csv文件c++解析性能

Posted

技术标签:

【中文标题】大csv文件c++解析性能【英文标题】:Big csv file c++ parsing performance 【发布时间】:2014-01-15 21:57:25 【问题描述】:

我有一个代表对称图(约 18kX18k)的大 csv 文件(25 mb)。在将其解析为向量数组时,我分析了代码(使用 VS2012 ANALYZER),它表明在读取每个字符(getline::basic_string::operator+=)时出现解析效率问题(总共约 19 秒)如下图所示:

这让我很沮丧,因为使用 Java 简单的缓冲行文件读取和标记器,我在不到半秒的时间内就实现了它。

我的代码只使用 STL 库:

int allColumns = initFirstRow(file,secondRow);
// secondRow has initialized with one value
int column = 1; // dont forget, first column is 0
VertexSet* rows = new VertexSet[allColumns];
rows[1] = secondRow;
string vertexString;
long double vertexDouble;
for (int row = 1; row < allColumns; row ++)
    // dont do the last row
    for (; column < allColumns; column++)
        //dont do the last column
        getline(file,vertexString,','); 
        vertexDouble = stold(vertexString);
        if (vertexDouble > _TH)
            rows[row].add(column);
        
    
    // do the last in the column
    getline(file,vertexString);
    vertexDouble = stold(vertexString);
    if (vertexDouble > _TH)
        rows[row].add(++column);
    
    column = 0;

initLastRow(file,rows[allColumns-1],allColumns);

init 第一行和最后一行基本上和上面的循环做同样的事情,但 initFirstRow 也会计算列数。

VertexSet 基本上是一个索引向量 (int)。每个读取的顶点(由“,”分隔)的长度不超过 7 个字符(值介于 -1 和 1 之间)。

【问题讨论】:

+1 用于根据实际分析结果而不是推测提出优化/性能问题! 您使用了哪些优化选项? (无 => 你的主要问题)。 尝试使用std::string::reserve 函数来减少重新分配的数量。 @fatsokol:我是 UNIX 人。我会使用-O2-O3。你似乎在使用 Visual Studio,而我对 Visual Studio 一无所知。您可能想尝试“发布”构建。但是,使用 iostreams 编译代码而不进行优化通常很慢(通常,没有优化的 C++ 代码往往会更慢)。 @DietmarKühl 我做了“发布”而不是“调试”,现在运行时间从 18+ 秒减少到 1+ 秒。 【参考方案1】:

25 兆字节,我猜你的文件是机器生成的。因此,您(可能)不需要担心诸如验证格式之类的事情(例如,每个逗号都已到位)。

鉴于文件的形状(即每一行都很长),您可能不会通过将每一行放入 stringstream 来解析数字而产生大量开销。

基于这两个事实,我至少会考虑编写一个将逗号视为空格的 ctype facet,然后使用该 facet 为 stringstream 注入一个区域设置,以便于解析数字。整体代码长度会稍长一些,但代码的每一部分最终都会非常简单:

#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <time.h>
#include <stdlib.h>
#include <locale>
#include <sstream>
#include <algorithm>
#include <iterator>

class my_ctype : public std::ctype<char> 
    std::vector<mask> my_table;
public:
    my_ctype(size_t refs=0):
        my_table(table_size),
        std::ctype<char>(my_table.data(), false, refs) 
    
        std::copy_n(classic_table(), table_size, my_table.data());
        my_table[',']=(mask)space;
    
;

template <class T>
class converter 
    std::stringstream buffer;
    my_ctype *m;
    std::locale l;
public:
    converter() : m(new my_ctype), l(std::locale::classic(), m)  buffer.imbue(l); 

    std::vector<T> operator()(std::string const &in) 
        buffer.clear();
        buffer<<in;
        return std::vector<T> std::istream_iterator<T>(buffer),
            std::istream_iterator<T>();        
    
;

int main() 
    std::ifstream in("somefile.csv");
    std::vector<std::vector<double>> numbers;

    std::string line;
    converter<double> cvt;

    clock_t start=clock();
    while (std::getline(in, line))
        numbers.push_back(cvt(line));
    clock_t stop=clock();
    std::cout<<double(stop-start)/CLOCKS_PER_SEC << " seconds\n";

为了对此进行测试,我生成了一个 1.8K x 1.8K 的 CSV 文件,其中包含如下所示的伪随机双精度:

#include <iostream>
#include <stdlib.h>

int main() 
    for (int i=0; i<1800; i++) 
        for (int j=0; j<1800; j++)
            std::cout<<rand()/double(RAND_MAX)<<",";
        std::cout << "\n";
    

这产生了一个大约 27 兆字节的文件。使用 gcc (g++ -O2 trash9.cpp) 编译读取/解析代码后,在我的笔记本电脑上进行的快速测试显示它在大约 0.18 到 0.19 秒内运行。它似乎从未使用(甚至接近)所有一个 CPU 内核,这表明它受 I/O 限制,因此在台式机/服务器机器(具有更快的硬盘驱动器)上,我希望它运行得更快。

【讨论】:

【参考方案2】:

这里的低效率在于微软对std::getline 的实现,它在代码中的两个地方使用。它的主要问题是:

    它一次从流中读取一个字符 一次将一个字符附加到字符串

原帖中的简介显示,这些问题中的第二个是本案中最大的问题。

我写了更多关于std::getlinehere效率低下的文章。

GNU 对std::getline 的实现,即libstdc++ 中的版本,要好得多。

遗憾的是,如果您希望您的程序快速运行并使用 Visual C++ 构建它,您将不得不使用比std::getline 更低级别的函数。

【讨论】:

【参考方案3】:

VS 中的调试运行时库非常慢,因为它会执行大量调试检查(针对越界访问和类似的事情)并调用许多在 Debug 中编译时未内联的非常小的函数。

在发行版中运行您的程序应该可以消除所有这些开销。

我认为下一个瓶颈是字符串分配。

【讨论】:

【参考方案4】:

我会尝试一次读取更大的内存块,然后全部解析。 喜欢..阅读整行。然后使用指针和专用函数解析这一行。

【讨论】:

从某种意义上说,这就是您使用库函数时应该发生的事情。我同意您可能可以使用这种方法进一步优化,但是基于可靠标准库的解决方案(启用优化)应该非常高效......【参考方案5】:

嗯,这里的答案很好。花了我一段时间,但我遇到了同样的问题。在此修复后,我的写入和处理时间从 38 秒变为 6 秒。 这就是我所做的。

首先使用 boost mmap 获取数据。然后,您可以使用 boost 线程在 boost mmap 返回的 const char* 上加快处理速度。像这样:(多线程因您的实现而异,因此我排除了该部分)

#include <boost/iostreams/device/mapped_file.hpp>
#include <boost/thread/thread.hpp>
#include <boost/lockfree/queue.hpp>

foo(string path)

    boost::iostreams::mapped_file mmap(path,boost::iostreams::mapped_file::readonly);
    auto chars = mmap.const_data(); // set data to char array
    auto eofile = chars + mmap.size(); // used to detect end of file
    string next = ""; // used to read in chars
    vector<double> data; // store the data
    for (; chars && chars != eofile; chars++) 
        if (chars[0] == ',' || chars[0] == '\n')  // end of value
            data.push_back(atof(next.c_str())); // add value
            next = ""; // clear
        
        else
            next += chars[0]; // add to read string
    

【讨论】:

更好的是使用二进制文件。

以上是关于大csv文件c++解析性能的主要内容,如果未能解决你的问题,请参考以下文章

C++ 中的 CSV 解析器不读取第一个元素

C++ 中快速、简单的 CSV 解析

MongoDB大数据高并发读写性能测试报告

Snapde和ExcelPowerPivotWPS打开超大CSV文件性能比较

java处理csv文件

NodeJS:读取一个大的 csv 文件