大csv文件c ++解析性能

时间:2013-12-28 14:01:34

标签: c++ csv stl io

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

这让我很沮丧,就像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之间)。

5 个答案:

答案 0 :(得分:5)

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

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

基于这两个事实,我至少考虑编写一个将逗号视为空格的ctype方面,然后使用该方面使用语言环境填充字符串流,以便轻松解析数字。整体代码长度会更大一些,但代码的每个部分最终都会非常简单:

#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绑定的,所以在桌面/服务器机器上(具有更快的硬盘驱动器)我希望它能够更快地运行。

答案 1 :(得分:2)

这里的效率低下是微软在std::getline的实现,它在代码的两个地方使用。它的关键问题是:

  1. 一次从流中读取一个字符
  2. 一次向字符串追加一个字符
  3. 原始帖子中的个人资料显示,在这种情况下,第二个问题是最大的问题。

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

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

    可悲的是,如果您希望程序快速并且使用Visual C ++构建它,则必须使用低于std::getline的低级函数。

答案 2 :(得分:1)

VS中的调试运行时库非常慢,因为它进行了大量的调试检查(对于超出范围的访问和类似的事情),并调用许多非常小的函数,这些函数在Debug中编译时没有内联。

在发布版中运行程序应该删除所有这些开销。

我对下一个瓶颈的赌注是字符串分配。

答案 3 :(得分:0)

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

答案 4 :(得分:0)

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

首先使用boost mmap获取数据。然后你可以使用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
    }
}