C++ 표현식 템플릿을 사용하여 값 배열에서 작동하는 SSE2 및 AVX 코드를 간단하게 작성하는 매우 간단한 프로그램을 테스트하고 있습니다.간단한 C++ 표현식 템플릿 내장 내장 함수는 다른 명령어를 생성합니다.
값 배열을 나타내는 svec
클래스가 있습니다.
SSE2 이중 레지스터를 나타내는 sreg
클래스가 있습니다.
expr
및 add_expr
은어레이의 추가를 나타냅니다.
컴파일러는 수식 코드 테스트 케이스와 비교하여 루프 당 3 개의 추가 명령어를 생성합니다. 이것에 대한 이유가 있는지 또는 동일한 출력을 생성하기 위해 컴파일러를 얻을 수있는 변경 사항이 있는지 궁금합니다.
전체 테스트 환경은 다음과 같습니다
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)
이가 참고 : 표현
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)
버전이 템플릿 : 손을 들어
#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;
}
이 지침은 루프를 압연 특별히 입증 할 수있는 최소한의 검증 가능한 코드. 이 코드는 2015 업데이트를 Visual Studio에서 내가 할인 한 3
아이디어를 기본 릴리스 빌드 설정을 사용하여 컴파일 :
손이 루프 롤 루프의 순서 (이미 전환하고 표현 컴파일러는 여전히 추가 지침을 삽입하고 않는 경우 루프 템플릿 컴파일러는 손
size
가 정전류 회로는 것을 추론 컴파일러를 방지constexpr
size
(나는 이미 시도 테스트 코드를 기반으로 루프를 압연 최적화되어)을 확인 핸드 롤 루프를 더 잘 최적화하기 위해서는 nt가 필요하며 핸드 롤 루프의 지침에는 아무런 차이가 없습니다).
첫 번째 추가 명령은 실제로 추가 명령이 아닙니다. 다른 사람들은 아마도 "완료"를 인쇄했을 것입니다. – Drop
@Drop, 나쁜 추측은 아닙니다. 루프의 순서를 바꾸는 코드를 컴파일하고 루프의 순서 템플릿 버전으로 루프의 순서와 관련된 컴파일러 문제를 피하기 위해 루프를 굴려서 불행하게도 아무런 차이가 없습니다. – keith
부수적으로 : 디스 어셈블리를 의미있게 만들기 위해서는 입력 데이터를 동적으로 제공하는 것이 좋습니다. 즉, 컴파일 타임에 입력을 알 수 없어야합니다. 스마트 컴파일러는 결과를 상수 표현으로 평가할 수 있다면 코드를 완전히 또는 부분적으로 최적화 할 수 있습니다 – Drop