gpt4 book ai didi

c++ - 循环展开 - G++ 与 Clang++

转载 作者:太空狗 更新时间:2023-10-29 21:33:01 27 4
gpt4 key购买 nike

我想知道是否值得用模板帮助编译器展开一个简单的循环。我准备了以下测试:

#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();
}

我首先尝试使用启用了 -O3 -funroll-loops 的 G++,我得到了 ( 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*),以及保存对象指针 (this) 的 edi/rdi。因此,您只会看到一堆 call 指令。试试value-initialize x(这将对 NodePtr 进行零初始化),

    T x{};

    你会得到更有意义的组装。

  2. Clang 似乎更擅长循环展开。参见,例如 this answer .循环是否值得展开由您决定。对于小循环,它们可能是。但你应该测量。

关于c++ - 循环展开 - G++ 与 Clang++,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53058998/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com