用C ++快速分离花车?

我有一个有数百万行的文件,每行有三个浮动空格分隔。 读取文件需要很长时间,所以我试图使用内存映射文件来读取它们,以发现问题不在于IO的速度,而在于解析速度。

我目前的解析是采取流(称为文件)并执行以下操作

float x,y,z;
file >> x >> y >> z;

有人在堆栈溢出推荐使用Boost.Spirit,但我找不到任何简单的教程来解释如何使用它。

我试图找到一个简单而有效的方法来解析看起来像这样的一行:

"134.32 3545.87 3425"

我会很感激一些帮助。 我想用strtok来分割它,但我不知道如何将字符串转换为浮点数,我不太确定这是最好的方法。

我不介意解决方案是否会提升。 我不介意它是否是最有效的解决方案,但我相信可以将速度提高一倍。

提前致谢。


如果转换是瓶颈(这很可能),您应该首先使用标准中的不同可能性。 从逻辑上讲,人们会期望他们非常接近,但实际上,他们并不总是:

  • 你已经确定std::ifstream太慢了。

  • 将你的内存映射数据转换为std::istringstream几乎肯定不是一个好的解决方案; 你首先必须创建一个字符串,它将复制所有的数据。

  • 编写自己的streambuf直接从内存中读取,而不进行复制(或使用不赞成使用的std::istrstream )可能是一种解决方案,但如果问题真的在于转换......这仍然使用相同的转换例程。

  • 您可以随时在您的内存映射流上尝试fscanfscanf 。 根据实现情况,它们可能比各种istream实现更快。

  • 可能比其中任何一个更快的是使用strtod 。 不需要为此标记: strtod跳过前导空格(包括'n' ),并且有一个out参数,它使第一个字符的地址不被读取。 结束条件有点棘手,你的循环可能看起来有点像:

  •     char* begin;    //  Set to point to the mmap'ed data...
                        //  You'll also have to arrange for a ''
                        //  to follow the data.  This is probably
                        //  the most difficult issue.
        char* end;
        errno = 0;
        double tmp = strtod( begin, &end );
        while ( errno == 0 && end != begin ) {
            //  do whatever with tmp...
            begin = end;
            tmp = strtod( begin, &end );
        }
    

    如果这些速度都不够快,则必须考虑实际数据。 它可能有一些额外的约束,这意味着你可以编写一个比一般更快的转换程序; 例如strtod必须处理固定的和科学的,即使有17位有效数字,它也必须是100%准确的。 它也必须是特定于语言环境的。 所有这些都增加了复杂性,这意味着需要添加代码才能执行。 但要小心:即使对于一组受限制的输入,编写一个高效且正确的转换例程也不是微不足道的; 你真的必须知道你在做什么。

    编辑:

    出于好奇,我已经进行了一些测试。 除了前面提到的解决方案之外,我还写了一个简单的自定义转换器,它只处理固定点(不科学),小数点后最多五位数字,小数点前的值必须符合int

    double
    convert( char const* source, char const** endPtr )
    {
        char* end;
        int left = strtol( source, &end, 10 );
        double results = left;
        if ( *end == '.' ) {
            char* start = end + 1;
            int right = strtol( start, &end, 10 );
            static double const fracMult[] 
                = { 0.0, 0.1, 0.01, 0.001, 0.0001, 0.00001 };
            results += right * fracMult[ end - start ];
        }
        if ( endPtr != nullptr ) {
            *endPtr = end;
        }
        return results;
    }
    

    (如果你真的使用了这个,你应该添加一些错误处理,这只是为了实验目的而快速打开,阅读我生成的测试文件,而不是其他任何东西。)

    界面与strtod完全相同,以简化编码。

    我在两种环境下运行基准测试(在不同的机器上,所以任何时候的绝对值都不相关)。 我得到了以下结果:

    在Windows 7下,使用VC 11(/ O2)编译:

    Testing Using fstream directly (5 iterations)...
        6.3528e+006 microseconds per iteration
    Testing Using fscan directly (5 iterations)...
        685800 microseconds per iteration
    Testing Using strtod (5 iterations)...
        597000 microseconds per iteration
    Testing Using manual (5 iterations)...
        269600 microseconds per iteration
    

    在Linux 2.6.18下,用g ++ 4.4.2(-O2,IIRC)编译:

    Testing Using fstream directly (5 iterations)...
        784000 microseconds per iteration
    Testing Using fscanf directly (5 iterations)...
        526000 microseconds per iteration
    Testing Using strtod (5 iterations)...
        382000 microseconds per iteration
    Testing Using strtof (5 iterations)...
        360000 microseconds per iteration
    Testing Using manual (5 iterations)...
        186000 microseconds per iteration
    

    在所有情况下,我读取554000行,每行有3个随机生成的[0...10000)范围内的浮点数。

    最显着的是Windows下fstreamfscan之间的巨大差异(以及fscanstrtod之间相对较小的差异)。 第二件事是简单的自定义转换函数在两个平台上获得多少。 必要的错误处理会稍微减慢它的差异,但差异仍然很大。 我预计会有一些改进,因为它不能处理标准转换例程所做的很多事情(如科学格式,非常非常小的数字,Inf和NaN,i18n等),但不是那么多。


    UPDATE

    由于Spirit X3可用于测试,因此我更新了基准。 与此同时,我使用Nonius来获得统计学上合理的基准。

    以下所有图表均可在线互动

    基准CMake项目+ testdata在github上使用:https://github.com/sehe/bench_float_parsing

    在这里输入图像描述

    概要:

    Spirit解析器是最快的。 如果你可以使用C ++ 14考虑实验版本Spirit X3:

    在这里输入图像描述

    以上是使用内存映射文件的措施。 使用IOstreams,它将会更慢,

    在这里输入图像描述

    但不像使用C / POSIX FILE*函数调用的scanf那么慢:

    在这里输入图像描述


    接下来的部分来自OLD的答案


    我实施了Spirit版本,并与其他建议的答案进行了比较。

    这是我的结果,所有测试都在相同的输入体( input.txt 515Mb)上运行。 请参阅下面的确切规格。


    (以秒为单位的挂钟时间,2次运行的平均值)

    令我惊讶的是,Boost Spirit的速度最快,最优雅:

  • 处理/报告错误
  • 支持+/- Inf和NaN以及变量空格
  • 完全没有问题检测输入的结束(而不是其他mmap答案)
  • 看起来不错:

    bool ok = phrase_parse(f,l,               // source iterators
         (double_ > double_ > double_) % eol, // grammar
         blank,                               // skipper
         data);                               // output attribute
    
  • 请注意, boost::spirit::istreambuf_iterator无法形容得慢得多(15s +)。 我希望这有帮助!

    基准详情

    所有的解析完成到vectorstruct float3 { float x,y,z; } struct float3 { float x,y,z; }

    使用生成输入文件

    od -f -A none --width=12 /dev/urandom | head -n 11000000
    

    这导致一个包含数据的515Mb文件

         -2627.0056   -1.967235e-12  -2.2784738e+33
      -1.0664798e-27  -4.6421956e-23   -6.917859e+20
      -1.1080849e+36   2.8909405e-33   1.7888695e-12
      -7.1663235e+33  -1.0840628e+36   1.5343362e-12
      -3.1773715e-17  -6.3655537e-22   -8.797282e+31
        9.781095e+19   1.7378472e-37        63825084
      -1.2139188e+09  -5.2464635e-05  -2.1235992e-38
       3.0109424e+08   5.3939846e+30  -6.6146894e-20
    

    编译程序使用:

    g++ -std=c++0x -g -O3 -isystem -march=native test.cpp -o test -lboost_filesystem -lboost_iostreams
    

    使用测量挂钟时间

    time ./test < input.txt 
    

    环境:

  • Linux桌面4.2.0-42-generic#49 -Ubuntu SMP x86_64
  • 英特尔(R)酷睿TM i7-3770K CPU @ 3.50GHz
  • 32GiB RAM
  • 完整代码

    旧代码的完整代码在本文的编辑历史中,最新版本位于github上


    在开始之前,请验证这是应用程序的缓慢部分,并获得测试工具,以便您可以测量改进。

    在我看来, boost::spirit会有点过分。 尝试fscanf

    FILE* f = fopen("yourfile");
    if (NULL == f) {
       printf("Failed to open 'yourfile'");
       return;
    }
    float x,y,z;
    int nItemsRead = fscanf(f,"%f %f %fn", &x, &y, &z);
    if (3 != nItemsRead) {
       printf("Oh dear, items aren't in the right format.n");
       return;
    }
    
    链接地址: http://www.djcxy.com/p/31515.html

    上一篇: separated floats in C++ quickly?

    下一篇: Why do std::string operations perform poorly?