给出定义:
#include <functional>
void call();
static constexpr inline auto nWrap(std::size_t n) {
return [n]() constexpr { for (std::size_t i = 0; i != n; ++i) call(); };
}
std::function<void()> oneCall() { return nWrap(1); }
std::function<void()> nCalls(std::size_t n) { return nWrap(n); }
我希望优化编译器能够返回一个循环展开/消除的函数。 然而
gcc、clang、msvc 和 icc 都创建带有循环的函数定义:
oneCall
事实上,涉及捕获常量(例如两个常量之和)的表达式似乎都是内联的。定义中是否有某些内容阻止内联? (因为所有编译器似乎都同意。)
如何定义
.L3:
call call()
addq $1, %rbx
cmpq 0(%rbp), %rbx
jne .L3
以便调用者可以内联捕获的常量? (即展开循环 iff 使用编译时间常数
n调用) 手动将 lambda 定义移至
nWrap
确实
消除了生成代码中的循环:
oneCall
constexpr const std::size_t n = 1;
return [n] { for (std::size_t i = 0; i != n; ++i) call(); };
模板化
std::_Function_handler<void (), oneCall()::{lambda()#1}>::_M_invoke(std::_Any_data const&):
jmp call()
不会,除了如果我使用
nWrap
的模板参数。添加或删除各种
n
、static
和 inline
影响不大。生产代码实际上将调用者提供的 lambda(或可调用)包装在 constexpr
中。 调用者 lambda
是内联的。然而,如果不定义两个单独的 lambda 函数,我找不到一种方法来消除常见单调用用例的循环。
,std::function
这里无法产生优化的返回值的原因是因为它会改变(非模板化)函数的返回
type。 这样,我发现将
nWrap
返回值嵌入到
另一个 lambda 定义(因此是一个不同的类型),将使编译器能够消除循环:
nCalls
中的结果始终返回优化的
static inline constexpr auto nWrap(std::size_t n) {
return [n]() { for (std::size_t i = 0; i != n; ++i) call(); };
};
std::function<void()> nCalls(std::size_t n) {
if (n == 1) {
return []{nWrap(1)();}; // Return value embedded into another lambda
}
return nWrap(n);
}
std::function<void()> oneCall() { return nCalls(1); }
和oneCall
在运行时决定返回什么——这不是我要求的,但这是我目前正在解决的问题。