为什么我在 C++ 中的 static_for 实现比常规 'for' 循环慢
Why is my static_for implementation in C++ slower than regular 'for' loop
我实现了一个 static_for
class 来利用编译器的效率,但是效率方面的结果完全不是我预期的。
class如下:
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(实际上是方差,但这不是 post 的重点):
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<1,1000>::申请< >
??$apply@V@@@?$static_for@$0A@$0DOI@@std@@SAXAEBV@@@Z 结束; std::static_for<0,1000>::应用< >
_文本结束
下次我看到它时,它看起来像这样:
; 19号线
移动 rcx, r11
jmp ??$apply@V@@@?$static_for@$0L@$0DOI@@std@@SAXAEBV@@@Z ; std::static_for<11,1000>::申请< >
??$apply@V@@@?$static_for@$00$0DOI@@std@@SAXAEBV@@@Z 结束; std::static_for<1,1000>::申请< >
_文本结束
请注意,它会移动到 static_for<11,1000>
,并展开 1 到 10。
然后是 21,从 11 开始到 20。以此类推,直到结束。
还有,一开始是寂寞的static_for<0,1000>
.
不确定这是否足以了解发生了什么。让我知道您可能还需要什么。
首先:您可以使用 Google 基准测试。我下载并将它编译成一个库 (MSVC),现在我可以将它添加到我的 benchamrking 项目中。它可以在线使用 (quick-bench.com),但它不允许 运行 长基准测试。
要比较汇编代码,可以使用编译器资源管理器。 (godbolt.org)
我会试试这个:
template <typename Lambda>
static inline constexpr void apply(Lambda&& f)
我发现这种元编程的引用传递有时会导致编译器做错事。
你也可以 FORCE_INLINE
:
#if defined(_MSC_VER)
#define FORCE_INLINE __forceinline
#else
#define FORCE_INLINE __attribute__((always_inline))
#endif
我实现了一个 static_for
class 来利用编译器的效率,但是效率方面的结果完全不是我预期的。
class如下:
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(实际上是方差,但这不是 post 的重点):
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<1,1000>::申请< > ??$apply@V@@@?$static_for@$0A@$0DOI@@std@@SAXAEBV@@@Z 结束; std::static_for<0,1000>::应用< > _文本结束
下次我看到它时,它看起来像这样:
; 19号线 移动 rcx, r11 jmp ??$apply@V@@@?$static_for@$0L@$0DOI@@std@@SAXAEBV@@@Z ; std::static_for<11,1000>::申请< > ??$apply@V@@@?$static_for@$00$0DOI@@std@@SAXAEBV@@@Z 结束; std::static_for<1,1000>::申请< > _文本结束
请注意,它会移动到 static_for<11,1000>
,并展开 1 到 10。
然后是 21,从 11 开始到 20。以此类推,直到结束。
还有,一开始是寂寞的static_for<0,1000>
.
不确定这是否足以了解发生了什么。让我知道您可能还需要什么。
首先:您可以使用 Google 基准测试。我下载并将它编译成一个库 (MSVC),现在我可以将它添加到我的 benchamrking 项目中。它可以在线使用 (quick-bench.com),但它不允许 运行 长基准测试。
要比较汇编代码,可以使用编译器资源管理器。 (godbolt.org)
我会试试这个:
template <typename Lambda>
static inline constexpr void apply(Lambda&& f)
我发现这种元编程的引用传递有时会导致编译器做错事。
你也可以 FORCE_INLINE
:
#if defined(_MSC_VER)
#define FORCE_INLINE __forceinline
#else
#define FORCE_INLINE __attribute__((always_inline))
#endif