Compiler Intrinsic을 사용해서 SIMD코드를 작성할때 주의할 점

어셈블리어로 SIMD를 다뤄보지 않고 Compiler Intrinsic으로 SIMD를 접하면 성능이 전혀 향상되지 않는 코드를 짜기 쉽다.

기본적으로 simd명령어로 존재하지 않는 기능은 1:1 치환이 불가능하다.

예를 들어 다음과 같은 코드는 simd명령으로 치환이 불가능하다.

a.m128_f32[0] = b.m128_f32[3];

3번째 성분을 0번째 성분으로 카피하는 명령어 같은건 존재하지 않으므로 범용 명령어의 mov기능만으로 구현된다.

a.m128_f32[0] = b.m128_f32[0] + c.m128_f32[0]

이런 코드의 경우 한샘플씩 더하는 addss명령이 있기 때문에 처리 가능은 하다. 가능하기도 하고 x64에선 xmm레지스터가 기본이라 sse지정 안해도, 그러니까 float a = b + c로 짜도 이미 컴파일러가 그렇게 코드를 만들어준다. 물론 성능상 이득은 없다.

_mm_set_ps()같은걸 사용하면 단지 set하는 정도니까 굉장히 비용이 쌀거라고들 생각하지만 그렇지 않다. 메모리로부터 16바이트 로드(movaps)하게 되므로 결코 싼 비용이 아니다.
최대한 메모리 억세스를 피하고 레지스터간 카피만 해야한다. 그러니까 0이나 -1따위의 상수는 명령어를 사용해서 레지스터 연산만으로 만들수 있으면 그렇게 하는게 좋다. 또 초반에 로드한 후에는 계속 재활용해야한다.

__m128 a = b; 라는 형태일 경우 b가 레지스터에 맵핑된게 확실하거나 스택안에 얼라이된 메모리로 잡혀있는게 확실할 경우는 movaps로, 얼라인되었음을 보장할 수 없는 메모리인 경우는 movups로 컴파일된다. movups는 compiler intrinsic에서_mm_load_ps()로 노출되어있지만 movaps는 명시적으로 노출되어있지 않다. 대입하는 경우에 상황에 따라 사용된다.

대부분의 compiler intrinsic은 실제 명령어와 1:1 대응되지만 편의를 위해서 만들어진 compiler intrinsic 함수도 많이 있다. 어셈명령어에 없는 compiler intrinsic 함수는 어떻게 컴파일되는지 디스어셈블된 코드를 보고 확인해야한다.

요약)
movaps제외하고 명시적으로 써넣은 compiler intrinsic이 어셈블리 명령어에 존재하는 경우에만 simd코드가 된다.


답글 남기기

아래 항목을 채우거나 오른쪽 아이콘 중 하나를 클릭하여 로그 인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Google+ photo

Google+의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

%s에 연결하는 중