当我使用 std::algorithms 而不是普通循环时,为啥这段代码会变慢?

Posted

技术标签:

【中文标题】当我使用 std::algorithms 而不是普通循环时,为啥这段代码会变慢?【英文标题】:Why this code is getting slower when I use std::algorithms instead of plain loops?当我使用 std::algorithms 而不是普通循环时,为什么这段代码会变慢? 【发布时间】:2015-04-29 12:24:29 【问题描述】:

我正在计算向量元素的均值和标准差。我有两个版本,我完全不明白为什么使用标准算法的版本比使用普通循环的版本慢。

两个版本都使用这个结构体作为返回类型:

struct MeanAndSigma 
    double mean;
    double sigma;
;

带有循环的版本是这样的:

MeanAndSigma getMeanAndSigma(const DVector& v)
    MeanAndSigma ms;
    ms.mean = 0;
    for (int i=0;i<v.size();++i)ms.mean += v[i];
    ms.mean = ms.mean / v.size();
    double sqsum = 0;
    for (int i=0;i<v.size();++i)sqsum += (v[i]-ms.mean)*(v[i]-ms.mean);
    ms.sigma = std::sqrt(sqsum / (v.size()-1));   
    return ms;

还有一个算法:

MeanAndSigma getMeanAndSigma2(const DVector& v)
    MeanAndSigma ms;
    ms.mean = std::accumulate(v.begin(),v.end(),0.0) / v.size();
    DVector diff(v.size());
    std::transform(v.begin(),v.end(),diff.begin(),
             std::bind2nd(std::minus<double>(), ms.mean));
    double sqsum = std::inner_product(diff.begin(),diff.end(),diff.begin(),0.0);
    ms.sigma = std::sqrt(sqsum / (v.size()-1));
    return ms;

当我使用具有 10k 个元素的向量测量每 10k 次调用所花费的时间时,对于带有循环的版本,我得到 ~2.0 秒,对于带有算法的版本,我得到 ~3.2 秒。这是为什么呢?

我已经比较了 cpu 时间和实时,但似乎两者都在单个 cpu 上运行(如预期的那样)。我在使用算法时做错了什么?

编辑:我并不是说这两个版本是等效的。尽管如此,我还是希望第二个版本会更快。正如 cmets 和答案中所指出的,第二个版本对元素使用了额外的迭代和额外的DVector(顺便说一句,只是typedef std::vector&lt;double&gt;)。但是,我对改进第二个版本的标准算法还不够熟悉。所以,现在我的问题是:

如何使用算法改进版本,使其比使用普通循环的版本更快?

【问题讨论】:

您是否在启用优化的情况下运行,因为像第二个函数这样的代码从优化中受益匪浅。如果您没有在启用优化的情况下运行,那么时间测量基本上是没有意义的。 @tobi303 那是个问题。当然,循环更快,它们做的工作更少(对于初学者来说,它们不会分配大的向量)。 真正的问题应该是“我如何使用标准算法实现这个计算?”顺便说一句,这是一个非常好的问题——std::inner_product 可以提供帮助。 @tobi303 基本上,getMeanAndSigma 需要 2 个向量循环,而 getMeanAndSigma2 需要 3 个循环,我认为这是根本原因。 @tobi303 将其替换为具有double operator()(double, double) const 运算符的struct 然后 @Mine:事实上,第二个解决方案需要 4 个循环,因为diff 的构造函数初始化了它所有的v.size() 条目。 【参考方案1】:

我不认为这些程序是等效的。在第二个版本(使用算法)中,填充了一个新的双精度向量,并且还涉及额外的迭代。

你可以试试这个(c++11版本),它相当于第一个版本。我没有尝试运行它,它应该可以进行一些小的更改。

MeanAndSigma getMeanAndSigma2(const DVector& v)
    MeanAndSigma ms;
    ms.mean = std::accumulate(v.begin(),v.end(),0.0) / v.size();
    double sqsum = std::accumulate(v.begin(),v.end(),
       [ms](double sum, double ve) return sum + (ve-ms.mean)*(ve-ms.mean);
    );
    ms.sigma = std::sqrt(sqsum / (v.size()-1));
    return ms;

没有 lambdas(未经测试,可能需要一些小改动)

class DiffSquare

    public:
        DiffSquare(double m) : _m(m) 
        double operator()(double sum, double e)
        
            return sum + (e - _m) * (e - _m);   
        
    private:
        double _m;
;

MeanAndSigma getMeanAndSigma2(const DVector& v) 
    MeanAndSigma ms;
    ms.mean = std::accumulate(v.begin(),v.end(),0.0) / v.size();
    DiffSquare diff_square(ms.mean);
    double sqsum = std::accumulate(v.begin(),v.end(),
        0.0,
        diff_square
    );
    ms.sigma = std::sqrt(sqsum / (v.size()-1));
    return ms;

【讨论】:

你会如何解决这个问题? @tobi303:您可以再次使用std::accumulate() 而不是std::transform(),提供您自己的BinaryOperation 函子,该函子在将其添加到总数之前进行额外的计算。 对不起,忘了说我不能使用 C++11。 然而,praetorian 已经为我指出了如何在没有 C++11 的情况下做到这一点。我只是想尝试一下,但我想我会接受你的回答,因为这个问题并没有说明关于 C++11 的任何内容 添加了非 lambda 版本,

以上是关于当我使用 std::algorithms 而不是普通循环时,为啥这段代码会变慢?的主要内容,如果未能解决你的问题,请参考以下文章

好消息!《森普周刊》正式成立啦

好消息!《森普周刊》正式成立啦

好消息!《森普周刊》正式成立啦

好消息!《森普周刊》正式成立啦

好消息!《森普周刊》正式成立啦

好消息!《森普周刊》正式成立啦