boost::transform和std::transform的区别

3
从下面的代码段中,我可以得出结论,std::transformboost::transform更加高效,因为前者使用的初始化和析构函数比后者少。
#include <algorithm>
#include <boost/range/algorithm.hpp>
class Ftor {
public:
    Ftor(const Ftor& rhs) : t(rhs.t)
    { std::cout << " Ftor : copy\n"; }
    Ftor(float rate) : t(rate)
    { std::cout << " Ftor : init\n"; }
    ~Ftor()
    { std::cout << "~Ftor : ...\n"; }
    float operator() (float x) { return x < t ? 0.0 : 1.0; }
private:
    float t;
};

typedef std::vector<float> vec_t;

int main (void) 
{
    vec_t arg(/*...*/);
    vec_t val(arg.size());
    float x = 1.0;
    /* Standard transform test */
    std::cout << "Standard transform:\n";
    std::transform(arg.begin(), arg.end(), val.begin(), Ftor(x));
    std::cout << "Boost transform:\n";
    /* Boost transform test */
    boost::transform(boost::make_iterator_range(arg.begin(), arg.end()),
                     val.begin(), Ftor(x));
}

输出:

Standard transform:
 Ftor : init
~Ftor : ...
Boost transform:
 Ftor : init
 Ftor : copy
~Ftor : ...
~Ftor : ...

标准转换使用2次调用。Boost转换使用4次调用。标准转换获胜了。但是它真的更好吗...

补充

正如@sehe所建议的,std::ref每次调用transform可以节省一个构造函数,而boost::transform只使用一次调用。但是std::ref不能以临时参数作为参数。但是,传递Ftor f(x)是可以的,因为后者具有明确的地址。

在循环内部调用transform时计算构造函数/析构函数调用的情况。现在我有两个选项:

std::cout << "with std::ref\n";
for (/*...*/) {
    x = ...;
    f = Ftor(x);
    boost::transform(arg, val.begin(), std::ref(f));
}

std::cout << "with temporary\n";
for (/*...*/) {
    x = ...;
    boost::transform(arg, val.begin(), Ftor(x));
}

输出:

with std::ref
 Ftor : init
 Ftor : init
 ...
~Ftor : ...
with temporary
 Ftor : init
 Ftor : copy
~Ftor : ...
~Ftor : ...
 Ftor : init
 Ftor : copy
~Ftor : ...
~Ftor : ...
 ...

我有一个gcc (Ubuntu 4.8.4-2ubuntu1~14.04) 4.8.4,使用或不使用-O3都会产生相同的结果。

构造函数/析构函数是否"昂贵"取决于operator()。在最终产品中,它将执行不太苛刻的数学运算,类似于上面的示例。

在coliru上的完整示例


修改几乎没有意义。为什么成员变量t在函数对象中甚至都没有被使用? - sehe
return (x<t?1.:0.); - rytis
天啊,我真的需要检查一下我的眼睛。不管怎样,正在等待基准代码(或者也许是代码试图实现的内容。可能有更好的算法方法)。 - sehe
关于编辑:那个完整的例子只是再次计算了不应该计算的东西。真正的_基准测试_在哪里? - sehe
1个回答

5

STL算法可以复制它们的谓词/函数对象。这主要是因为它们是按值传递的。

你会发现boost多调用了一个前向调用。

有问题吗?

通常情况下没有问题。编译器非常擅长内联、复制省略和依赖分析。

很可能生成的代码最终完全相同。

当然,添加cout语句会完全破坏这一点。比较不带构造函数/析构函数副作用的生成的代码,看看差异!

使用无副作用的公平比较,STL和Boost变体生成的代码完全相同http://paste.ubuntu.com/14544891/

修复

STL(和boost算法)的设计方式允许您显式地通过引用传递函数对象,如果您想要。您可以使用std::ref来实现:

在Coliru上实时运行

#include <algorithm>
#include <vector>
#include <iostream>
#include <functional>
#include <boost/range/algorithm.hpp>

class Ftor {
  public:
    Ftor(const Ftor &rhs) : t(rhs.t) { std::cout << " Ftor : copy" << std::endl; } 
    Ftor(float rate) : t(rate)       { std::cout << " Ftor : init" << std::endl; } 
    ~Ftor()                          { std::cout << "~Ftor : ..."  << std::endl; } 
    float operator()(float x)        { return x; }  

  private:
    float t;
};

typedef std::vector<float> vec_t;

int main(void) {
    vec_t arg(190, 1), val(arg.size());

    {
        std::cout << "STL transform: " << std::endl;
        Ftor f(1.0);
        std::transform(arg.begin(), arg.end(), val.begin(), std::ref(f));
    }
    std::cout << "-----\n";

    {
        std::cout << "Boost transform: " << std::endl;
        Ftor f(1.0);
        boost::transform(arg, val.begin(), std::ref(f));
    }
    std::cout << "-----\n";
}

打印
STL transform: 
Ftor : init
~Ftor : ...
-----
Boost transform: 
Ftor : init
~Ftor : ...
-----

注意:无论如何,在容器上使用范围算法并构造一个范围boost::make_iterator_range(arg.begin(), arg.end()),而不是仅使用arg,这是非常具有讽刺意味的。

boost::transform(arg, val.begin(), Ftor(x));

1
公平比较不使用副作用会为STL和Boost变体生成相同的代码: http://paste.ubuntu.com/14544891/ - sehe
1
因为公平比较和讽刺的评论而点赞...以及讽刺的备注 :D - rytis
如果我将接近结尾的行 boost::transform(arg, val.begin(), std::ref(f)); 替换为 boost::transform(arg, val.begin(), std::ref(Ftor(1.0)));,我会得到一个错误:使用已删除的函数‘void std::ref(const _Tp&&) [with _Tp = Ftor]’。这是怎么回事? - rytis
我自己来回答:A赢得了胜利。解决方案:在一个循环中,我进行了N次迭代,得到以下操作(将每个构造函数和析构函数计为1): A:N(初始化)+ 1(...)= N+1个操作。 B:N(初始化)+ N(复制)+ 2N(...)= 4N个操作。这太惊人了,我从未想到会有这么大的差异! - rytis
没有具体的基准代码,我们无法发表任何意见。有了基准代码,我今晚稍后会重新审查。现在你正在计算任意操作,这些操作可能一开始甚至不需要(显著的)时间。 - sehe
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接