std :: function的性能开销是多少?


74

我在一个论坛上听到使用std::function<>原因导致性能下降。是真的吗 如果为真,性能会大大下降吗?


34
会导致性能下降相比,有什么选择?
弗雷德·纽克

4
您将必须比user408141更加具体。
Lightness Races in Orbit

4
确实,这是一个很糟糕的问题。
2011年

我对标题进行了编辑,使其更有意义。至于“相比于什么”-与手推式通用解决方案相比可比...
UncleBens 2011年

哦,对不起,我太笨拙了!:D

Answers:


16

您可以从boost的参考资料中找到信息:通过boost :: function进行调用会产生多少开销?表现

这不能确定增强功能的“是或否”。考虑到程序的要求,性能下降可能是可以接受的。通常,程序的某些部分不是关键性能。即使那样也可以接受。这只是您可以确定的事情。

对于标准库版本,该标准仅定义一个接口。使它起作用完全取决于各个实现。我想将使用类似的实现来增强功能。


93

实际上,std:function在使用它时必须考虑到性能问题。它的主要优势(std::function即类型擦除机制)并非免费提供,我们可能(但不一定必须)为此付出代价。

std::function是包装可调用类型的模板类。但是,它不是可调用类型本身的参数,而是仅在其返回和参数类型上的参数。可调用类型仅在构造时才知道,因此,std::function不能具有此类型的预先声明的成员来保存提供给其构造函数的对象的副本。

粗略地说(实际上,事情要复杂得多)std::function只能持有指向传递给其构造函数的对象的指针,这会引起生命周期问题。如果指针指向寿命小于该对象寿命的std::function对象,则内部指针将悬空。为防止此问题,std::function可以通过调用operator new(或自定义分配器)在堆上复制对象。人们最常将动态内存分配称为性能暗示std::function

我最近写了一篇文章,提供了更多详细信息,并说明了如何(以及在​​何处)避免付出内存分配的代价。

高效使用Lambda表达式和std :: function


因此,这描述了构建/销毁a的开销std::functionboost::function声明有关调用性能的信息:“使用适当的内联编译器,对函数对象的调用需要通过函数指针进行一次调用。如果该调用是对自由函数指针的调用,则必须对该函数指针进行额外的调用(除非编译器具有非常强大的过程间分析)。”
mucaho 2015年

动态分配是否仅执行一次?我的意思是,一旦初始化,它的执行效果是否与使用函数指针完全相同?
本杰明·巴鲁瓦

值得注意的是,包装对象是否很小(例如,Linux上std :: function不超过16个字节)并且打开了小对象优化功能,则std :: function不会尝试进行任何堆分配。请注意,必须使用std :: cref或std :: ref来包装传入的参数,以避免在调用树期间进行复制。在这种情况下,对于没有太多参数的函数,例如std :: shared_ptr; 一个简单的原语;等等,没有堆分配。如果使用简单的参数包装一些lambda,这将特别有用。
Alex Suo

您的链接显示为损坏(重定向至drdobbs.com主页)
俄罗斯

@Ruslan可悲的是。不幸的是,DrDobbs几年前就关闭了,我不知道旧内容正在发生什么。我在任何地方都找不到我的文章。我为此感到遗憾和悲伤:-(
卡西欧·内里

12

这在很大程度上取决于是否要传递不带任何参数(不分配堆空间)的函数。

还取决于其他因素,但这是主要因素。

的确,您需要比较的东西,不能简单地说,与根本不使用它相比,它“减少了开销”,您需要将它与使用替代方法传递函数进行比较。如果您完全不需要使用它,那么从一开始就不需要它


3
如果实现使用小缓冲区优化将函数对象存储在std::function实例中,并且所传递的可调用对象在SBO的合适大小之内,则即使是绑定参数也可能不会导致动态分配。
underscore_d

12

首先,函数内部的开销变小了;工作负载越高,开销越小。

其次:与虚拟函数相比,g ++ 4.5没有任何区别:

main.cc

#include <functional>
#include <iostream>

// Interface for virtual function test.
struct Virtual {
    virtual ~Virtual() {}
    virtual int operator() () const = 0;
};

// Factory functions to steal g++ the insight and prevent some optimizations.
Virtual *create_virt();
std::function<int ()> create_fun();
std::function<int ()> create_fun_with_state();

// The test. Generates actual output to prevent some optimizations.
template <typename T>
int test (T const& fun) {
    int ret = 0;
    for (int i=0; i<1024*1024*1024; ++i) {
        ret += fun();
    }    
    return ret;
}

// Executing the tests and outputting their values to prevent some optimizations.
int main () {
    {
        const clock_t start = clock();
        std::cout << test(*create_virt()) << '\n';
        const double secs = (clock()-start) / double(CLOCKS_PER_SEC);
        std::cout << "virtual: " << secs << " secs.\n";
    }
    {
        const clock_t start = clock();
        std::cout << test(create_fun()) << '\n';
        const double secs = (clock()-start) / double(CLOCKS_PER_SEC);
        std::cout << "std::function: " << secs << " secs.\n";
    }
    {
        const clock_t start = clock();
        std::cout << test(create_fun_with_state()) << '\n';
        const double secs = (clock()-start) / double(CLOCKS_PER_SEC);
        std::cout << "std::function with bindings: " << secs << " secs.\n";
    }
}

隐含cc

#include <functional>

struct Virtual {
    virtual ~Virtual() {}
    virtual int  operator() () const = 0;
};
struct Impl : Virtual {
    virtual ~Impl() {}
    virtual int  operator() () const { return 1; }
};

Virtual *create_virt() { return new Impl; }

std::function<int ()> create_fun() { 
    return  []() { return 1; };
}

std::function<int ()> create_fun_with_state() { 
    int x,y,z;
    return  [=]() { return 1; };
}

输出g++ --std=c++0x -O3 impl.cc main.cc && ./a.out

1073741824
virtual: 2.9 secs.
1073741824
std::function: 2.9 secs.
1073741824
std::function with bindings: 2.9 secs.

所以,不要害怕。如果您的设计/可维护性可以通过优先std::function于虚拟调用而得到改善,请尝试使用它们。就个人而言,我真的很喜欢在类的客户端上不强制接口和继承的想法。


2
@Xeo:是的。但是,验证比相信要好:)当您不使用优化时,相同的测试与会显示1:3的差异std::function,因此该测试并非完全没有道理。
塞巴斯蒂安·马赫2012年

1
使用G ++ 4.8.2,我始终获得2.9、3.3和3.3秒的时间。如果我添加,-flto它们全部变为3.3。我的完全疯狂的猜测是,GCC实际上试图进行优化std::function(类似于-flto虚拟函数所获得的功能),但是这种优化实际上却有害。
2014年

3
使用g ++ 5.3,我得到2.0、2.3、2.3(-O2); 0.7、2.0、2.0(-O2 -flto); 2.3、2.3、2.3(-O2 -flto -fno-devirtualize); 2.0、2.3、2.3(-O2 -fno-devirtualize)。因此,似乎在较新的g ++版本中取消虚拟化已得到足够的改进,以至于不再是取消优化。
Paul Brannan

3
g ++ 6.3.0:g ++ -std = gnu ++ 14 -O3 -flto -march = native impl.cpp main.cpp && ./a.out 1073741824虚拟:1.97619秒。1073741824 std :: function:6.86855 secs。1073741824 std :: function with bindings:6.86847 secs。
亚历山德拉·佩雷拉·纳内斯

2
Ubuntu 18.04(AMD 2400G)上的g ++ 7.4.0:`g ++ --std = c ++ 17 -O3 impl.cc main.cc &&。/ a.out`:虚拟:1.38742秒,std :: function:1.44681秒,带有绑定的std :: function:1.39367秒。
安德烈·凯斯利
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.