代码之家  ›  专栏  ›  技术社区  ›  AOK

为什么C++中的static\u for实现比常规的“for”循环慢

  •  -3
  • AOK  · 技术社区  · 7 年前

    我实现了 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>{}); }
    };
    }
    

    为了测试这是否真的更有效,我计算了随机生成的双精度向量的平均值和标准差(实际上是方差,但这不是文章的重点):

    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\u for@$00$0DOI@@std@@SAXAEBV@@@Z;std::static\u for<11000>::应用(<)&燃气轮机; ??$apply@V@@@?$static\u for@$0A@$0DOI@@std@@SAXAEBV@@@Z ENDP;std::static\u for<01000>::应用(<)&燃气轮机; _文本结尾

    下次我看到这个的时候,它看起来是这样的:

    ; 第19行 mov rcx,r11 jmp$apply@V@@@?$@$0L@$0DOI@@std@@SAXAEBV@@@Z的静态\u;std::static\u for<111000>::应用(<)&燃气轮机; ??$apply@V@@@?$static\u for@$00$0DOI@@std@@SAXAEBV@@@Z ENDP;std::static\u for<11000>::应用(<)&燃气轮机; _文本结尾

    请注意,它将移动到 static_for<11,1000> ,然后展开1到10。 然后是21,从11到20。等等,直到最后。 而且,一开始有一种孤独 static_for<0,1000> .

    不确定这是否是了解情况的足够起点。请告诉我您还需要什么。

    2 回复  |  直到 7 年前
        1
  •  2
  •   keith    7 年前

    我想试试这个:

    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
    
        2
  •  1
  •   Robert Andrzejuk    7 年前

    第一:你可以使用谷歌基准测试。我下载并将其编译成一个lib(MSVC),现在我可以将其添加到我的benchamrking项目中。它可以在线(quickbench.com)获得,但不允许运行长时间的基准测试。

    要比较汇编代码,可以使用编译器资源管理器。(godbolt.org)