为什么我在C ++中的static_for实现比常规'for'循环慢

时间:2018-01-29 10:37:22

标签: c++ metaprogramming template-meta-programming

我实现了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.所以

  1. 我如何确认我的假设,即代码没有内联;
  2. 我该如何解决这个问题?
  3. 添加ASM代码:

    所以看一下ASM(~147k行),我发现了以下内容:

    第19行     jmp ?? $ apply @ V @@@?$ static_for @ $ 00 $ 0DOI @@ std @@ SAXAEBV @@@ Z;的std :: static_for&LT; 1,1000&GT; ::应用&LT; &GT; ?? $ apply @ V @@@?$ static_for @ $ 0A @ $ 0DOI @@ std @@ SAXAEBV @@@ Z ENDP;的std :: static_for&LT; 0,1000&GT; ::应用&LT; &GT; _TEXT ENDS

    下次我看到它时看起来像这样:

    第19行     mov rcx,r11     jmp ?? $ apply @ V @@@?$ static_for @ $ 0L @ $ 0DOI @@ std @@ SAXAEBV @@@ Z;的std :: static_for&LT; 11,1000&GT; ::应用&LT; &GT; ?? $ apply @ V @@@?$ static_for @ $ 00 $ 0DOI @@ std @@ SAXAEBV @@@ Z ENDP;的std :: static_for&LT; 1,1000&GT; ::应用&LT; &GT; _TEXT ENDS

    请注意,它会移至static_for<11,1000>,然后展开1到10。 然后21,从11到20开始。依此类推,直到结束。 此外,在开始时有一个寂寞的static_for<0,1000>

    不确定这是否足以看到发生了什么。让我知道你还需要什么。

2 个答案:

答案 0 :(得分:2)

我会试试这个:

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

答案 1 :(得分:1)

首先:您可以使用Google Benchmark。我下载并编译成lib(MSVC),现在我可以将它添加到我的benchamrking项目中。它可以在线获得(quick-bench.com),但它不允许运行很长的基准测试。

要比较汇编代码,编译器资源管理器是要使用的工具。 (godbolt.org)