我正在测试一个非常简单的程序,该程序使用C ++表达式模板来简化编写在值数组上运行的SSE2和AVX代码。
我有一个类svec
,它代表一组值。
我有一个代表SSE2双重寄存器的类sreg
。
我有expr
和add_expr
代表svec
数组的添加。
与手动代码相比,编译器为每个循环生成三个额外的指令用于表达式模板测试用例。我想知道是否有这样的理由,或者我可以做出任何改变以使编译器生成相同的输出?
完整的测试工具是:
#include <iostream>
#include <emmintrin.h>
struct sreg
{
__m128d reg_;
sreg() {}
sreg(const __m128d& r) :
reg_(r)
{
}
sreg operator+(const sreg& b) const
{
return _mm_add_pd(reg_, b.reg_);
}
};
template <typename T>
struct expr
{
sreg operator[](std::size_t i) const
{
return static_cast<const T&>(*this).operator[](i);
}
operator const T&() const
{
return static_cast<const T&>(*this);
}
};
template <typename A, typename B>
struct add_expr : public expr<add_expr<A, B>>
{
const A& a_;
const B& b_;
add_expr(const A& a, const B& b) :
a_{ a }, b_{ b }
{
}
sreg operator[](std::size_t i) const
{
return a_[i] + b_[i];
}
};
template <typename A, typename B>
inline auto operator+(const expr<A>& a, const expr<B>& b)
{
return add_expr<A, B>(a, b);
}
struct svec : public expr<svec>
{
sreg* regs_;
std::size_t size_;
svec(std::size_t size) :
size_{ size }
{
regs_ = static_cast<sreg*>(_aligned_malloc(size * 32, 32));
}
~svec()
{
_aligned_free(regs_);
}
template <typename T>
svec& operator=(const T& expression)
{
for (std::size_t i = 0; i < size(); i++)
{
regs_[i] = expression[i];
}
return *this;
}
const sreg& operator[](std::size_t index) const
{
return regs_[index];
}
sreg& operator[](std::size_t index)
{
return regs_[index];
}
std::size_t size() const
{
return size_;
}
};
static constexpr std::size_t size = 64;
int main()
{
svec a(size);
svec b(size);
svec c(size);
svec d(size);
svec vec(size);
//hand rolled loop
for (std::size_t j = 0; j < size; j++)
{
vec[j] = a[j] + b[j] + c[j] + d[j];
}
//expression templates version of hand rolled loop
vec = a + b + c + d;
std::cout << "Done...";
std::getchar();
return EXIT_SUCCESS;
}
对于手卷循环,说明如下:
00007FF621CD1B70 mov r8,qword ptr [c]
00007FF621CD1B75 mov rdx,qword ptr [b]
00007FF621CD1B7A mov rax,qword ptr [a]
00007FF621CD1B7F vmovupd xmm0,xmmword ptr [rcx+rax]
00007FF621CD1B84 vaddpd xmm1,xmm0,xmmword ptr [rdx+rcx]
00007FF621CD1B89 vaddpd xmm3,xmm1,xmmword ptr [r8+rcx]
00007FF621CD1B8F lea rax,[rcx+rbx]
00007FF621CD1B93 vaddpd xmm1,xmm3,xmmword ptr [r10+rax]
00007FF621CD1B99 vmovupd xmmword ptr [rax],xmm1
00007FF621CD1B9D add rcx,10h
00007FF621CD1BA1 cmp rcx,400h
00007FF621CD1BA8 jb main+0C0h (07FF621CD1B70h)
对于表达式模板版本:
00007FF621CD1BC0 mov rdx,qword ptr [c]
00007FF621CD1BC5 mov rcx,qword ptr [rcx]
00007FF621CD1BC8 mov rax,qword ptr [r8]
00007FF621CD1BCB vmovupd xmm0,xmmword ptr [r9+rax]
00007FF621CD1BD1 vaddpd xmm1,xmm0,xmmword ptr [rcx+r9]
00007FF621CD1BD7 vaddpd xmm0,xmm1,xmmword ptr [rdx+r9]
00007FF621CD1BDD lea rax,[r9+rbx]
00007FF621CD1BE1 vaddpd xmm0,xmm0,xmmword ptr [rax+r10]
00007FF621CD1BE7 vmovupd xmmword ptr [rax],xmm0
00007FF621CD1BEB add r9,10h
00007FF621CD1BEF cmp r9,400h
00007FF621CD1BF6 jae main+154h (07FF621CD1C04h) # extra instruction 1
00007FF621CD1BF8 mov rcx,qword ptr [rsp+60h] # extra instruction 2
00007FF621CD1BFD mov r8,qword ptr [rsp+58h] # extra instruction 3
00007FF621CD1C02 jmp main+110h (07FF621CD1BC0h)
请注意,这是专门用于演示问题的最低可验证代码。代码是使用Visual Studio 2015 Update 3中的默认版本构建设置编译的。
我打折的想法:
循环的顺序(我已经切换了手动循环和表达式模板循环以检查编译器是否仍然插入了额外的指令而且确实如此)
编译器正在根据constexpr
size
优化手卷循环(我已经尝试过测试代码,阻止编译器推断size
是常量以更好地优化手卷循环,它与手卷循环的说明没有区别。)
答案 0 :(得分:3)
两个循环似乎每次迭代都重新加载数组指针。 (例如,第一个循环中的mov r8, [c]
)。第二个版本只是更低效地执行它,间接有两个级别。其中一个在循环结束时,在一个条件分支后退出循环。
请注意,您未将其中的一条更改为“&#34; new&#34;是mov rcx, [rcx]
。循环之间的寄存器分配是不同的,但那些是数组起始指针。它(以及商店后的rcx,[rsp+60h]
)正在替换mov rax,qword ptr [a]
。我假设a
也是RSP的偏移量,实际上并不是静态存储的标签。
可能这种情况正在发生,因为MSVC ++在别名分析方面没有成功,以证明vec[j]
中的商店无法修改任何指针。我没有仔细查看您的模板,但如果您要引入一个额外的间接级别,您希望优化它,那么问题就是它不是。
显而易见的解决方案是使用更好地优化的编译器。 clang3.9运行良好(自动向量化,没有重新加载指针),gcc完全优化它,因为没有使用结果。
但是,如果您仍然坚持使用MSVC,请查看是否存在任何有用的严格别名选项或无别名关键字或声明。例如GNU C ++扩展包括__restrict__
以获得相同的&#34;这不是别名&#34;行为为C99&n restrict
个关键字。 IDK,如果MSVC支持这样的话。
挑剔:
拨打jae
&#34;额外&#34;是不对的。指令。它只是来自jb
的相反谓词,所以它现在是while(true){ ... if() break; reload; }
循环而不是更高效的do{...}while()
循环。 (我使用C语法来显示asm循环结构。显然,如果你真的编译了那些C循环,编译器可以优化它们。)所以,如果有的话,&#34;额外的指令&#34;是无条件的分支,JMP。