簡體   English   中英

循環展開 - G ++與Clang ++

[英]Loop unrolling - G++ vs. Clang++

我想知道是否值得幫助編譯器使用模板來展開一個簡單的循環。 我准備了以下測試:

#include <cstdlib>
#include <utility>
#include <array>

class TNode
{
public:
  void Assemble();
  void Assemble(TNode const *);
};

class T
{
private:
  std::array<TNode *,3u> NodePtr;

private:
  template <std::size_t,std::size_t>
  void foo() const;

  template <std::size_t... ij>
  void foo(std::index_sequence<ij...>) const
    { (foo<ij%3u,ij/3u>(),...); }

public:
  void foo() const
    { return foo(std::make_index_sequence<3u*3u>{}); }

  void bar() const;
};

template <std::size_t i,std::size_t j>
inline void T::foo() const
{
if constexpr (i==j)
  NodePtr[i]->Assemble();
else
  NodePtr[i]->Assemble(NodePtr[j]);
}

inline void T::bar() const
{
for (std::size_t i= 0u; i<3u; ++i)
  for (std::size_t j= 0u; j<3u; ++j)
    if (i==j)
      NodePtr[i]->Assemble();
    else
      NodePtr[i]->Assemble(NodePtr[j]);
}

void foo()
{
T x;
x.foo();
}

void bar()
{
T x;
x.bar();
}

我首先嘗試使用G ++並啟用了-O3 -funroll-loops然后我得到了( https://godbolt.org/z/_Wyvl8 ):

foo():
        push    r12
        push    rbp
        push    rbx
        sub     rsp, 32
        mov     r12, QWORD PTR [rsp]
        mov     rdi, r12
        call    TNode::Assemble()
        mov     rbp, QWORD PTR [rsp+8]
        mov     rsi, r12
        mov     rdi, rbp
        call    TNode::Assemble(TNode const*)
        mov     rbx, QWORD PTR [rsp+16]
        mov     rsi, r12
        mov     rdi, rbx
        call    TNode::Assemble(TNode const*)
        mov     rsi, rbp
        mov     rdi, r12
        call    TNode::Assemble(TNode const*)
        mov     rdi, rbp
        call    TNode::Assemble()
        mov     rsi, rbp
        mov     rdi, rbx
        call    TNode::Assemble(TNode const*)
        mov     rsi, rbx
        mov     rdi, r12
        call    TNode::Assemble(TNode const*)
        mov     rdi, rbp
        mov     rsi, rbx
        call    TNode::Assemble(TNode const*)
        add     rsp, 32
        mov     rdi, rbx
        pop     rbx
        pop     rbp
        pop     r12
        jmp     TNode::Assemble()
bar():
        push    r13
        push    r12
        push    rbp
        xor     ebp, ebp
        push    rbx
        sub     rsp, 40
.L9:
        mov     r13, QWORD PTR [rsp+rbp*8]
        xor     ebx, ebx
        lea     r12, [rbp+1]
.L5:
        cmp     rbp, rbx
        je      .L15
        mov     rsi, QWORD PTR [rsp+rbx*8]
        mov     rdi, r13
        add     rbx, 1
        call    TNode::Assemble(TNode const*)
        cmp     rbx, 3
        jne     .L5
        mov     rbp, r12
        cmp     r12, 3
        jne     .L9
.L16:
        add     rsp, 40
        pop     rbx
        pop     rbp
        pop     r12
        pop     r13
        ret
.L15:
        mov     rdi, r13
        mov     rbx, r12
        call    TNode::Assemble()
        cmp     r12, 3
        jne     .L5
        mov     rbp, r12
        cmp     r12, 3
        jne     .L9
        jmp     .L16

我無法閱讀匯編,但我似乎明白模板版本會展開循環,而bar有循環和分支。

然后我嘗試使用Clang ++( https://godbolt.org/z/VCNb65 ),我得到了一個非常不同的圖片:

foo():                                # @foo()
        push    rax
        call    TNode::Assemble()
        call    TNode::Assemble(TNode const*)
        call    TNode::Assemble(TNode const*)
        call    TNode::Assemble(TNode const*)
        call    TNode::Assemble()
        call    TNode::Assemble(TNode const*)
        call    TNode::Assemble(TNode const*)
        call    TNode::Assemble(TNode const*)
        pop     rax
        jmp     TNode::Assemble()    # TAILCALL
bar():                                # @bar()
        push    rax
        call    TNode::Assemble()
        call    TNode::Assemble(TNode const*)
        call    TNode::Assemble(TNode const*)
        call    TNode::Assemble(TNode const*)
        call    TNode::Assemble()
        call    TNode::Assemble(TNode const*)
        call    TNode::Assemble(TNode const*)
        call    TNode::Assemble(TNode const*)
        pop     rax
        jmp     TNode::Assemble()    # TAILCALL

這里發生了什么? 最終的裝配如何如此簡潔?

  1. NodePtr未初始化,當您使用它時,它是UB。 所以優化器可以做任何想做的事情:這里它決定省略對寄存器esi/rsi賦值,它用於將參數傳遞給TNode::Assemble(TNode const*) ,並傳遞給edi/rdi ,它保存一個對象指針( this )。 因此,您只能看到一堆call說明。 嘗試對x進行值初始化 (這將零初始化NodePtr ),

     T x{}; 

    你會得到更有意義的裝配。

  2. Clang似乎更適合循環展開。 參見,例如, 這個答案 您可以自行決定循環是否值得展開。 對於小循環,它們可能是。 但你應該衡量。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM