避免 std::function 的开销
Avoiding the overheads of std::function
我想 运行 对(自定义)单链表中的元素进行一组操作。遍历链表和运行操作的代码很简单,但是重复,如果到处都是copy/pasted,可能会出错。性能和仔细的内存分配在我的程序中很重要,所以我想避免不必要的开销。
我想写一个包装器来包含重复的代码并封装要对链表的每个元素进行的操作。由于操作中发生的函数各不相同,我需要捕获必须提供给操作的多个变量(在实际代码中),所以我查看了使用 std::function
。在这个示例代码中所做的实际计算在这里没有意义。
#include <iostream>
#include <memory>
struct Foo
{
explicit Foo(int num) : variable(num) {}
int variable;
std::unique_ptr<Foo> next;
};
void doStuff(Foo& foo, std::function<void(Foo&)> operation)
{
Foo* fooPtr = &foo;
do
{
operation(*fooPtr);
} while (fooPtr->next && (fooPtr = fooPtr->next.get()));
}
int main(int argc, char** argv)
{
int val = 7;
Foo first(4);
first.next = std::make_unique<Foo>(5);
first.next->next = std::make_unique<Foo>(6);
#ifdef USE_FUNC
for (long i = 0; i < 100000000; ++i)
{
doStuff(first, [&](Foo& foo){ foo.variable += val + i; /*Other, more complex functionality here */ });
}
doStuff(first, [&](Foo& foo){ std::cout << foo.variable << std::endl; /*Other, more complex and different functionality here */ });
#else
for (long i = 0; i < 100000000; ++i)
{
Foo* fooPtr = &first;
do
{
fooPtr->variable += val + i;
} while (fooPtr->next && (fooPtr = fooPtr->next.get()));
}
Foo* fooPtr = &first;
do
{
std::cout << fooPtr->variable << std::endl;
} while (fooPtr->next && (fooPtr = fooPtr->next.get()));
#endif
}
如果运行为:
g++ test.cpp -O3 -Wall -o mytest && time ./mytest
1587459716
1587459717
1587459718
real 0m0.252s
user 0m0.250s
sys 0m0.001s
而如果 运行 为:
g++ test.cpp -O3 -Wall -DUSE_FUNC -o mytest && time ./mytest
1587459716
1587459717
1587459718
real 0m0.834s
user 0m0.831s
sys 0m0.001s
这些计时在多个 运行 中相当一致,并且在使用 std::function
时显示 4 倍乘数。有没有更好的方法可以做我想做的事?
使用模板:
template<typename T>
void doStuff(Foo& foo, T const& operation)
对我来说:
mvine@xxx:~/mikeytemp$ g++ test.cpp -O3 -DUSE_FUNC -std=c++14 -Wall -o mytest && time ./mytest
1587459716
1587459717
1587459718
real 0m0.534s
user 0m0.529s
sys 0m0.005s
mvine@xxx:~/mikeytemp$ g++ test.cpp -O3 -std=c++14 -Wall -o mytest && time ./mytest
1587459716
1587459717
1587459718
real 0m0.583s
user 0m0.583s
sys 0m0.000s
函数对象非常重,但在负载非常大(>10000 个周期)或需要多态性的情况下使用,例如在通用作业调度程序中。
它们需要包含您的可调用对象的副本并处理它可能抛出的任何异常。
使用模板让您更接近金属,因为生成的代码经常被内联。
template <typename Func>
void doStuff(Foo& foo, Func operation)
{
Foo* fooPtr = &foo;
do
{
operation(*fooPtr);
} while (fooPtr->next && (fooPtr = fooPtr->next.get()));
}
编译器将能够查看您的函数内部并消除冗余。
在 Golbolt 上,您的内部循环变为
.LBB0_6: # =>This Loop Header: Depth=1
lea edx, [rax + 7]
mov rsi, rcx
.LBB0_7: # Parent Loop BB0_6 Depth=1
add dword ptr [rsi], edx
mov rsi, qword ptr [rsi + 8]
test rsi, rsi
jne .LBB0_7
mov esi, eax
or esi, 1
add esi, 7
mov rdx, rcx
.LBB0_9: # Parent Loop BB0_6 Depth=1
add dword ptr [rdx], esi
mov rdx, qword ptr [rdx + 8]
test rdx, rdx
jne .LBB0_9
add rax, 2
cmp rax, 100000000
jne .LBB0_6
作为奖励,如果您不使用链表,循环可能会完全消失。
我想 运行 对(自定义)单链表中的元素进行一组操作。遍历链表和运行操作的代码很简单,但是重复,如果到处都是copy/pasted,可能会出错。性能和仔细的内存分配在我的程序中很重要,所以我想避免不必要的开销。
我想写一个包装器来包含重复的代码并封装要对链表的每个元素进行的操作。由于操作中发生的函数各不相同,我需要捕获必须提供给操作的多个变量(在实际代码中),所以我查看了使用 std::function
。在这个示例代码中所做的实际计算在这里没有意义。
#include <iostream>
#include <memory>
struct Foo
{
explicit Foo(int num) : variable(num) {}
int variable;
std::unique_ptr<Foo> next;
};
void doStuff(Foo& foo, std::function<void(Foo&)> operation)
{
Foo* fooPtr = &foo;
do
{
operation(*fooPtr);
} while (fooPtr->next && (fooPtr = fooPtr->next.get()));
}
int main(int argc, char** argv)
{
int val = 7;
Foo first(4);
first.next = std::make_unique<Foo>(5);
first.next->next = std::make_unique<Foo>(6);
#ifdef USE_FUNC
for (long i = 0; i < 100000000; ++i)
{
doStuff(first, [&](Foo& foo){ foo.variable += val + i; /*Other, more complex functionality here */ });
}
doStuff(first, [&](Foo& foo){ std::cout << foo.variable << std::endl; /*Other, more complex and different functionality here */ });
#else
for (long i = 0; i < 100000000; ++i)
{
Foo* fooPtr = &first;
do
{
fooPtr->variable += val + i;
} while (fooPtr->next && (fooPtr = fooPtr->next.get()));
}
Foo* fooPtr = &first;
do
{
std::cout << fooPtr->variable << std::endl;
} while (fooPtr->next && (fooPtr = fooPtr->next.get()));
#endif
}
如果运行为:
g++ test.cpp -O3 -Wall -o mytest && time ./mytest
1587459716
1587459717
1587459718
real 0m0.252s
user 0m0.250s
sys 0m0.001s
而如果 运行 为:
g++ test.cpp -O3 -Wall -DUSE_FUNC -o mytest && time ./mytest
1587459716
1587459717
1587459718
real 0m0.834s
user 0m0.831s
sys 0m0.001s
这些计时在多个 运行 中相当一致,并且在使用 std::function
时显示 4 倍乘数。有没有更好的方法可以做我想做的事?
使用模板:
template<typename T>
void doStuff(Foo& foo, T const& operation)
对我来说:
mvine@xxx:~/mikeytemp$ g++ test.cpp -O3 -DUSE_FUNC -std=c++14 -Wall -o mytest && time ./mytest
1587459716
1587459717
1587459718
real 0m0.534s
user 0m0.529s
sys 0m0.005s
mvine@xxx:~/mikeytemp$ g++ test.cpp -O3 -std=c++14 -Wall -o mytest && time ./mytest
1587459716
1587459717
1587459718
real 0m0.583s
user 0m0.583s
sys 0m0.000s
函数对象非常重,但在负载非常大(>10000 个周期)或需要多态性的情况下使用,例如在通用作业调度程序中。
它们需要包含您的可调用对象的副本并处理它可能抛出的任何异常。
使用模板让您更接近金属,因为生成的代码经常被内联。
template <typename Func>
void doStuff(Foo& foo, Func operation)
{
Foo* fooPtr = &foo;
do
{
operation(*fooPtr);
} while (fooPtr->next && (fooPtr = fooPtr->next.get()));
}
编译器将能够查看您的函数内部并消除冗余。
在 Golbolt 上,您的内部循环变为
.LBB0_6: # =>This Loop Header: Depth=1
lea edx, [rax + 7]
mov rsi, rcx
.LBB0_7: # Parent Loop BB0_6 Depth=1
add dword ptr [rsi], edx
mov rsi, qword ptr [rsi + 8]
test rsi, rsi
jne .LBB0_7
mov esi, eax
or esi, 1
add esi, 7
mov rdx, rcx
.LBB0_9: # Parent Loop BB0_6 Depth=1
add dword ptr [rdx], esi
mov rdx, qword ptr [rdx + 8]
test rdx, rdx
jne .LBB0_9
add rax, 2
cmp rax, 100000000
jne .LBB0_6
作为奖励,如果您不使用链表,循环可能会完全消失。