c++ - 使用 istream_iterators 构造 vector 比使用 string/std::atof 的 while 循环慢,为什么?

标签 c++ linux performance optimization g++

我正在从 std::cin 读取值流,并从中构造一个 std::vector 。最初,我使用了带有临时 std::string 对象的 while 循环,并将 std::atof 与临时字符串中的 c_str() 一起使用。那里有一些电话,而且通常有很多事情发生。我用范围构造函数替换了它,使用 std::istream_iterator 和 std::cin ,认为它看起来更简单,而且更快。令我惊讶的是,虽然看起来确实更干净,但速度有点慢。

我的问题是这样的:为什么在下面的代码中,使用 std::istream_iterator 构造 std::vector 比使用函数调用混搭的替代方法慢?另外,是否有一种方法可以使用 std::istreambuf_iterator 来修改范围构造,以使两种方法的性能等效?我看到答案指出我应该将 std::ios_base::sync_with_stdio(false); 添加到代码中。虽然这提高了性能,但在两种情况下都是如此,并且两种方法之间仍然存在差异。

最小工作示例:

#include <iostream>
#include <iterator>
#include <string>
#include <vector>

using namespace std;

int main()
{
    /* Faster Method */
    string temporary_line{};
    vector<double> data{};
    while(cin>> temporary_line)
        data.push_back(atof(temporary_line.c_str()));  

    /* Slower Method */
    //vector<double> data{ istream_iterator<double>{cin},
    //                     stream_iterator<double>{} };

    cout<< data.back() << '\n';
}

我通过 5 个不同的编译器(g++-{7,8} 和 clang++-{6,7,8})运行了代码。所有运行的代码均在 -O2 下编译,每次代表 5 次运行的平均值。时间很紧,增加更多的试验并不重要。结果显示所有编译器的行为相同,g++ 在这两种方法上仅比 clang++ 领先了一小部分。

enter image description here

要进行测试,请创建一个包含约 1,000,000 个随机整数的文件:

$ for i in {0..999999};执行 echo $RANDOM >> 数据文件;完成

编译:

$ g++ -o ds descriptive_statistics.cpp -O2

使用生成的示例数据运行:

$ time cat 数据文件 | ./ds

完整代码:

#include <algorithm>
#include <iostream>
#include <iterator>
#include <cmath>
#include <cstdint>
#include <cstdlib>
#include <memory>
#include <numeric>
#include <string>
#include <vector>

class DS {

public:
    DS() = default;
    DS(const DS& ) = default;
    DS(DS&& ) = default;
    DS(const double*, std::size_t length);
    DS(const double*, const double*);
    virtual ~DS() = default;

    DS& operator=(const DS& ) = default;
    DS& operator=(DS&& ) = default;

    friend std::ostream& operator<<(std::ostream& , const DS& );

    bool operator<(const DS& ) = delete;
    bool operator>(const DS& ) = delete;
    bool operator==(const DS& ) = delete;

private:
    double min;
    double first_quartile;
    double mean;
    double median;
    double third_quartile;
    double max;
    double sum;
    double variance;
    double standard_deviation;
};

DS::DS(const double* begin, const double* end) {

    const std::size_t size = std::distance(begin, end) ;

    min = *begin;    

    first_quartile = begin[ size/4 ] ;

    sum = std::accumulate(begin, end, double{});

    mean = sum / size ;

    const std::size_t idx{ size  / 2 };
    median = begin[ idx ] ;
    if( ! (size & 1) ) {      
        median += begin[ idx - 1 ];
        median *= 0.5;
    }

    third_quartile = begin[ 3*size/4 ] ; 

    variance = std::accumulate(begin, end, double{}, 
            [&] (double a, double b) {
                return a + std::pow(b - mean, 2.0); 
            }) / size ;

    standard_deviation = std::sqrt(variance);  

    max = *std::prev(end);   
}

DS::DS(const double* begin, std::size_t length) {
    const double* end = begin + length;
    *this = DS(begin,end);
}

std::ostream& operator<<(std::ostream& os, const DS& ds) {
    os <<  ds.min                << '\n'
       <<  ds.first_quartile     << '\n'
       <<  ds.mean               << '\n'
       <<  ds.median             << '\n'
       <<  ds.third_quartile     << '\n'
       <<  ds.max                << '\n'
       <<  ds.sum                << '\n'
       <<  ds.variance           << '\n'
       <<  ds.standard_deviation << '\n';
    return os;
}

int main(int argc, char** argv)
{
    // This section is faster than the section below
    std::string temporary_line{};
    std::vector<double> data{};
    while(std::cin>> temporary_line) {
        data.push_back(std::atof(temporary_line.c_str()));
    }

    // This section is slower than the section above
    // std::vector<double> data{
    //         std::istream_iterator<double>{std::cin},
    //         std::istream_iterator<double>{}
    // };

    if(! std::is_sorted(data.cbegin(), data.cend()) ) {
        std::sort(data.begin(), data.end());
    }

    DS ds(&*data.cbegin(), &*data.cend());   
    std::cout<< ds << std::endl;

    return(EXIT_SUCCESS);
}

最佳答案

看看 std::istream_iterator<double> 的实现你可以注意到这样做

std::vector<double> data{ std::istream_iterator<double>{file},
                             std::istream_iterator<double>{} };

实际上相当于做

double temporary_line;
std::vector<double> data{};
while (file>>temporary_line) {
      data.push_back(temporary_line);
}

查看 godbolt 上汇编代码的差异

所以你的整个问题可以归结为为什么 std::atofoperator>> 快.

正如您在使用 gcc 的 O2 中注意到的那样,有一个对 strtod 的调用而不是call std::basic_istream<char, std::char_traits<char> >& std::basic_istream<char, std::char_traits<char> >::_M_extract<double>(double&) https://www.godbolt.org/z/Od-FIk但代码结构基本相同。

我相信时差的原因是 localestd::atof部分忽略了 locale (它看到 C 语言环境)另一方面 operator>>使用指定 C++ locale 的约束进行解析工作最终使用 UNICODE 编码器。

进行更复杂的操作需要更多时间。但是考虑 UNICODE 和每个 locale 会产生 50% 的惩罚时间你不觉得那很糟糕吗?

关于c++ - 使用 istream_iterators 构造 vector 比使用 string/std::atof 的 while 循环慢,为什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57939299/

相关文章:

php - 使用 PHP 执行 .exe 文件

javascript - 强制窗口对开发区分大小写

javascript - Ionic 2 滚动性能问题

c++ - 指向动态分配数组的静态指针

C++ 成员函数

c++ - 在结构中定义 vector 的长度

php - MySQL - 字符串索引

c++代码在python中使用ctypes和mmap进行转换

linux - 使用正则表达式查找目录的命令

c# - LINQ 的 Enumerable.Take 方法在速度方面是否与 SQL 的 TOP 相当?