【发布时间】:2018-01-29 10:37:22
【问题描述】:
我实现了一个static_for 类来利用编译器效率,但效率方面的结果完全不是我所期望的。
类如下:
namespace std{
template <int First, int Last>
class static_for
{
public:
template <typename Lambda>
static inline constexpr void apply(Lambda const& f)
{
if (First < Last)
{
f(std::integral_constant<int, First>{});
static_for<First + 1, Last>::apply(f);
}
}
};
template <int N>
class static_for<N, N>
{
public:
template <typename Lambda>
static inline constexpr void apply(Lambda const& f) { f(std::integral_constant<int, N>{}); }
};
}
为了测试这是否真的更有效,我计算了一个随机生成的双精度向量的 avg 和 sd(实际上是方差,但这不是帖子的重点):
std::vector<double> t;
std::vector<double> t1;
std::vector<double> t2;
std::srand((unsigned)time(NULL));
for (int i = 0; i < 10000000; ++i) {
int b = std::rand() % 20 + 1;
t.push_back(b);
}
//Static-for
for (int i = 0; i < 9000000; ++i)
{
double N = 0;
double avg = 0;
double sd = 0;
std::static_for<0, 1000>::apply([&](auto j)
{
avg += t[i + j.value];
sd += t[i + j.value] * t[i + j.value];
++N;
});
avg /= N;
sd /= N;
sd -= avg * avg;
t1.push_back(sd);
}
//Dynamic-for
for (int i = 0; i < 9000000; ++i)
{
double N = 0;
double avg = 0;
double sd = 0;
for (int j = 0; j <= 1000; ++j)
{
avg += t[i + j];
sd += t[i + j] * t[i + j];
++N;
}
avg /= N;
sd /= N;
sd -= avg * avg;
t2.push_back(sd);
}
注意:如果造成模板深度问题,您可以将 1000 更改为较小的数字,因为这不是重点。
我希望第一部分的执行速度比第二部分快,但事实并非如此。我认为编译器会强制对每个单独的static_for<X,Y>::apply 进行动态调用,而不是将代码内联。
我正在使用 Visual C++ 2017。所以
- 如何确认我的假设,即代码没有被内联;
- 我该如何解决这个问题?
添加 ASM 代码:
所以查看 ASM(大约 147k 行)我发现以下内容:
; 19 号线 jmp ??$apply@V@@@?$static_for@$00$0DOI@@std@@SAXAEBV@@@Z ; std::static_for::apply ??$apply@V@@@?$static_for@$0A@$0DOI@@std@@SAXAEBV@@@Z ENDP ; std::static_for::apply _TEXT 结束
下次我看到这个时,它看起来像这样:
; 19 号线 mov rcx, r11 jmp ??$apply@V@@@?$static_for@$0L@$0DOI@@std@@SAXAEBV@@@Z ; std::static_for::apply ??$apply@V@@@?$static_for@$00$0DOI@@std@@SAXAEBV@@@Z ENDP ; std::static_for::apply _TEXT 结束
请注意,它会移动到 static_for<11,1000>,并展开 1 到 10。
然后是 21,从 11 到 20。以此类推,直到结束。
还有,一开始有一个寂寞的static_for<0,1000>。
不确定这是否足以让我们了解正在发生的事情。让我知道您可能还需要什么。
【问题讨论】:
-
可能的答案:调用函数 1000 次的成本比增加变量 1000 次的成本要高。调用函数有它的开销。
-
发布minimal reproducible example。发布您的编译器标志。检查生成的程序集。如果没有更多信息,无法真正给您答案。
-
for (int i = 0; i < some_constant; ++i)是一种极为常见的模式。不要期望比优化编译器更聪明。 -
我不认为将这个循环展开成 ~11k 指令(至少 using GCC 会发生这种情况)比普通的 ~10 指令循环更快-loop 给你。
-
另外,不要向
std命名空间添加东西; it is undefined behavior to do so.
标签: c++ metaprogramming template-meta-programming