C++ C++;函数调用lambda obj,按值调用比按引用调用快
测试代码是这样的,计时器将输出销毁时经过的时间C++ C++;函数调用lambda obj,按值调用比按引用调用快,c++,lambda,C++,Lambda,测试代码是这样的,计时器将输出销毁时经过的时间 uint64_t res1 = 0, res2 = 0; void test_accumulate_bind_function(uint64_t& x, uint64_t i) { x += i; } uint64_t res1 = 0, res2 = 0, res3 = 0, res4=0; template <typename Function> vo
uint64_t res1 = 0, res2 = 0;
void test_accumulate_bind_function(uint64_t& x, uint64_t i)
{
x += i;
}
uint64_t res1 = 0, res2 = 0, res3 = 0, res4=0;
template <typename Function>
void do_loop_ref(Function & func, const uint64_t upper_limit = 100000)
{
for (uint64_t i = 0; i < upper_limit; ++i)
func(i);
}
template <typename Function>
void do_loop_forward(Function && func, const uint64_t upper_limit = 100000)
{
Function f(std::forward<Function>(func));
for (uint64_t i = 0; i < upper_limit; ++i)
f(i);
}
template <typename Function>
void do_loop_copy(Function func, const uint64_t upper_limit = 100000)
{
for (uint64_t i = 0; i < upper_limit; ++i)
func(i);
}
void test_bind_copy()
{
{
namespace arg = std::placeholders;
uint64_t x = 0;
auto accumulator = std::bind(&test_accumulate_bind_function, std::ref(x), arg::_1);
std::cout << "reference:";
timer t;
do_loop_ref(accumulator);
res1 = x;
}
{
namespace arg = std::placeholders;
uint64_t x = 0;
auto accumulator = std::bind(&test_accumulate_bind_function, std::ref(x), arg::_1);
std::cout << "copy:";
timer t;
do_loop_copy(accumulator);
res2 = x;
}
{
namespace arg = std::placeholders;
uint64_t x = 0;
auto accumulator = std::bind(&test_accumulate_bind_function, std::ref(x), arg::_1);
std::cout << "localcopy:";
timer t;
do_loop_forward(accumulator);
res3 = x;
}
{
namespace arg = std::placeholders;
uint64_t x = 0;
auto accumulator = std::bind(&test_accumulate_bind_function, std::ref(x), arg::_1);
std::cout << "move:";
timer t;
do_loop_forward(std::move(accumulator));
res4 = x;
}
printf("res1:%lld, res2:%lld, res3:%lld, res4:%lld\n", res1, res2, res3, res4);
}
void test_copy()
{
{
uint64_t x = 0;
auto accumulator = [&x](uint64_t i){ return x += i; };
std::cout << "reference:";
timer t;
do_loop_ref(accumulator);
res1 = x;
}
{
uint64_t x = 0;
auto accumulator = [&x](uint64_t i){ return x += i; };
std::cout << "copy:";
timer t;
do_loop_copy(accumulator);
res2 = x;
}
{
uint64_t x = 0;
auto accumulator = [&x](uint64_t i){ return x += i; };
std::cout << "localcopy:";
timer t;
do_loop_forward(accumulator);
res3 = x;
}
{
uint64_t x = 0;
auto accumulator = [&x](uint64_t i){ return x += i; };
std::cout << "move:";
timer t;
do_loop_forward(std::move(accumulator));
res4 = x;
}
printf("res1:%lld, res2:%lld, res3:%lld, res4:%lld\n", res1, res2, res3, res4);
}
int main()
{
test_copy();
test_bind_copy();
}
uint64\u t res1=0,res2=0;
无效测试累积绑定函数(uint64\U t&x,uint64\U t i)
{
x+=i;
}
uint64_t res1=0,res2=0,res3=0,res4=0;
模板
无效循环参考(函数和函数,常数64上限=100000)
{
对于(uint64_t i=0;i<上限;++i)
func(i);
}
模板
无效循环前进(函数和函数,常数64上限=100000)
{
函数f(std::forward(func));
对于(uint64_t i=0;i<上限;++i)
f(i);
}
模板
无效循环复制(函数func,常数64上限=100000)
{
对于(uint64_t i=0;i<上限;++i)
func(i);
}
无效测试绑定副本()
{
{
名称空间arg=std::占位符;
uint64_t x=0;
自动累加器=std::bind(&test\u accumulate\u bind\u函数,std::ref(x),arg::\u 1);
std::cout您是否在编译优化打开的情况下运行基准测试?
在gcc中使用-O3
进行编译时,这两个代码在汇编中看起来完全相同(因此应该执行完全相同的操作)
在没有任何优化标志的情况下,代码看起来还是一样的,只是在通过引用传递时有一个额外的间接级别
// pass by reference
mov rax, QWORD PTR [rbp-24]
// pass by value
lea rax, [rbp-32]
std::function
在内部只是包装函数内部状态的结构(lambda中捕获的变量)。通过引用传递函数
可以想象为传递一个指向结构的指针,其中包含该函数捕获的所有状态。因此,调用方和被调用方在同一内存位置上工作。当通过值传递时,调用方和被调用方具有独立的副本,并且一个副本上的写入和读取不会影响另一个副本
在上述情况下,当函数按值传递时,地址按原样加载,而当其按引用传递时,在调用lambda之前会读取额外的内存。检查
额外的间接级别可能会发挥诸如引用位置之类的作用,但这将与执行此操作的硬件紧密耦合。但这在很大程度上取决于如何编译此代码、在何处执行代码以及如何度量它(您尚未共享计时器的代码).因此,由于问题中的信息有限,我会将额外的阅读时间归咎于此。我添加了两个测试用例,但结果令人困惑,传递值与移动到本地f相同,传递引用与复制到本地f相同“std::function在内部只是包装函数内部状态的结构(lambda情况下捕获的变量)”不是真的,不是。std::function
是一个类型擦除器,而不是包装器,因此不知道存储函数的类型,更不用说它的状态了(例如,如您所述捕获的lambda变量).@bolov同意。但我不想偏离这个问题。类型擦除需要更长的脚注。我想强调的是,捕获的状态将随函数在函子对象中移动。另外,std::function是否使用类型擦除或其他魔法由实现来决定。@bashrc请放弃请原谅我吹毛求疵,但是根据定义,std::function
是一个类型擦除类。一个函数指针,一个定义了operator()
的类型,一个没有捕获的lambda或一个有捕获的lambda(都接受2个int参数并返回一个int)-所有存储到同一类型:std::function
。这是类型擦除。有关原始类型的信息丢失。用于实现这一点的类型擦除技术确实留给了实现(很可能是void*
+多态性的一种形式)。