为什么我在 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。所以

  1. 如何确认我的假设,即代码未被内联;
  2. 我该如何解决这个问题?

添加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