Развертывание петли — 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

Что здесь случилось? Как полученная сборка может быть такой краткой?

3

Решение

  1. NodePtr не инициализируется, и когда вы используете его, это UB. Таким образом, оптимизатор может делать все, что захочет: здесь он решает опустить назначения в регистр esi/rsi, который используется для передачи аргумента TNode::Assemble(TNode const*)и к edi/rdi, который содержит указатель на объект (this). В результате вы видите только кучу call инструкции.
    Пробовать Значение инициализации x (это будет нулевая инициализация NodePtr),

    T x{};
    

    и вы получите гораздо более значимое собрание.

  2. Кажется, Clang лучше раскручивается в петле. Смотрите, например, этот ответ. Вам решать, стоит ли раскручивать петли. Для маленьких петель, наверное, они есть. Но вы должны измерить.

2

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]