2011-05-26 59 views
6

我正在创建一个多维向量(数学向量),我允许基本的数学运算+, - ,/,*,=。模板接受两个参数,一个是类型(int,float等),另一个是向量的大小。目前我正通过for循环应用这些操作。现在考虑编译时已知的大小,编译器是否会展开循环?如果没有,有没有办法展开它没有(或最小)的性能损失?编译器将展开此循环吗?

template <typename T, u32 size> 
class Vector 
{ 
public: 
    // Various functions for mathematical operations. 
    // The functions take in a Vector<T, size>. 
    // Example: 
    void add(const Vector<T, size>& vec) 
    { 
     for (u32 i = 0; i < size; ++i) 
     { 
      values[i] += vec[i]; 
     } 
    } 

private: 
    T values[size]; 
}; 

之前有人评论Profile then optimize请注意,这是对我的3D图形引擎的基础,它必须要快。其次,我想知道为了教育自己。

+4

不健全表露无疑,但如果你用优化编译和转储组装你几乎可以找到:) – Skurmedel 2011-05-26 16:18:17

+1

即使它必须是真快,分析是一个非常宝贵的工具。事实上,这是你的朋友*特别是*如果必须非常快,因为它比猜测好得多。 (另外,答案可能在很大程度上取决于编译器,所使用的标志以及甚至更多)。 – delnan 2011-05-26 16:18:18

+0

'如果不是,是否有一种方法可以展开而不会影响性能?“展开一个循环是为了提高性能......如果性能降低,你为什么要这样做? – Chad 2011-05-26 16:18:21

回答

9

您可以通过反汇编来查看如何编译特定代码。

Vector<int, 16> a, b; 
    Vector<int, 65536> c, d; 

    asm("xxx"); // marker 
    a.Add(b); 
    asm("yyy"); // marker 
    c.Add(d); 
    asm("zzz"); // marker 

现在编译

gcc -O3 1.cc -S -o 1.s 

而看到DISASM

xxx 
# 0 "" 2 
#NO_APP 
    movdqa 524248(%rsp), %xmm0 
    leaq 524248(%rsp), %rsi 
    paddd 524184(%rsp), %xmm0 
    movdqa %xmm0, 524248(%rsp) 
    movdqa 524264(%rsp), %xmm0 
    paddd 524200(%rsp), %xmm0 
    movdqa %xmm0, 524264(%rsp) 
    movdqa 524280(%rsp), %xmm0 
    paddd 524216(%rsp), %xmm0 
    movdqa %xmm0, 524280(%rsp) 
    movdqa 524296(%rsp), %xmm0 
    paddd 524232(%rsp), %xmm0 
    movdqa %xmm0, 524296(%rsp) 
#APP 
# 36 "1.cc" 1 
    yyy 
# 0 "" 2 
#NO_APP 
    leaq 262040(%rsp), %rdx 
    leaq -104(%rsp), %rcx 
    xorl %eax, %eax 
    .p2align 4,,10 
    .p2align 3 
.L2: 
    movdqa (%rcx,%rax), %xmm0 
    paddd (%rdx,%rax), %xmm0 
    movdqa %xmm0, (%rdx,%rax) 
    addq $16, %rax 
    cmpq $262144, %rax 
    jne .L2 
#APP 
# 38 "1.cc" 1 
    zzz 

正如你看到的,第一圈是小到足以得到展开。第二个是循环。

+0

伟大的诀窍,卓越的洞察力。 – 2012-11-01 11:00:07

0

许多编译器将展开此循环,不知道您所指的“编译器”是否会。世界上不只有一个编译器。

如果你想保证它被展开,那么TMP(带内联)就可以做到这一点。 (这实际上是TMP的一个比较普通的应用程序,通常用作元编程的一个例子)。

+0

我已添加相关标签。它是MSVC编译器。 – Samaursa 2011-05-26 16:22:59

+0

@Samarusa:没有“编译器”。你知道有多少味道的MSVC?将版本字符串(在运行'cl.exe/version'时由编译器打印)添加到您的问题中。 – 2011-05-26 16:27:39

4

第一:现代CPU对于预测分支非常聪明,所以展开循环可能没有帮助(甚至可能会伤害到)。

第二:是的,现代编译器知道如何展开这样的循环,如果它是你的目标CPU的好主意。

第三:现代编译器甚至可以自动矢量化循环,这比展开更好。底线:除非你知道关于CPU架构的lot,否则不要以为你比你的编译器更聪明。以简单直接的方式编写您的代码,直到您的分析器告诉您,不用担心微优化。

1

首先,展开循环是非常有益的。

您的问题的唯一可能的答案是“它取决于”(在编译器标志上,值为size等)。

如果你真的想知道,请问你的编译器:编译成汇编代码,其典型值为size,并且使用真正使用的优化标志,然后检查结果。

1

解决这个问题的唯一方法是使用您自己的优化参数在您自己的编译器上尝试它。做一个测试文件,你的“它展开”代码,test.cpp

#include "myclass.hpp" 

void doSomething(Vector<double, 3>& a, Vector<double, 3>& b) { 
    a.add(b); 
} 

然后参考代码片段reference.cpp

#include "myclass.hpp" 

void doSomething(Vector<double, 3>& a, Vector<double, 3>& b) { 
    a[0] += b[0]; 
    a[1] += b[1]; 
    a[2] += b[2]; 
} 

而现在使用GCC编译它们吐出仅装配:

for x in *.cpp; do g++ -c "$x" -Wall -Wextra -O2 -S -o "out/$x.s"; done 

根据我的经验,当使用在编译时知道持续时间的循环时,GCC默认展开3或更小的循环;使用-funroll-loops将导致它展开更多。

1

循环可以使用递归模板实例化展开。在你的C++实现中这可能会也可能不会更快。

我稍微调整了你的例子,以便编译。

typedef unsigned u32; // or something similar 

template <typename T, u32 size> 
class Vector 
{ 
    // need to use an inner class, because member templates of an 
    // unspecialized template cannot be explicitly specialized. 
    template<typename Vec, u32 index> 
    struct Inner 
    { 
    static void add(const Vec& a, const Vec& b) 
    { 
     a.values[index] = b.values[index]; 
     // triggers recursive instantiation of Inner 
     Inner<Vec, index-1>::add(a,b); 
    } 
    }; 
    // this specialization terminates the recursion 
    template<typename Vec> 
    struct Inner<Vec, 0> 
    { 
    static void add(const Vec& a, const Vec& b) 
    { 
     a.values[0] = b.values[0]; 
    } 
    }; 

public: 

    // PS! this function should probably take a 
    // _const_ Vector, because the argument is not modified 
    // Various functions for mathematical operations. 
    // The functions take in a Vector<T, size>. 
    // Example: 
    void add(Vector<T, size>& vec) 
    { 
     Inner<Vector, size-1>::add(*this, vec); 
    } 

    T values[size]; 
};