2011-09-01 7 views
10

때때로 MSVC 2010이 SSE 지침을 전혀 변경하지 않는다는 것을 알고 있습니다. 컴파일러가 최선을 다룰 때부터 루프 내에서 명령어 순서에 신경 쓸 필요가 없다고 생각했습니다.SSE 마이크로 최적화 명령 주문

어떻게 생각하나요? 무엇이 가장 좋은 명령 순서를 결정합니까? 일부 명령어는 다른 명령어보다 지연 시간이 길고 일부 명령어는 CPU 레벨에서 병렬/비동기로 실행될 수 있음을 알고 있습니다. 어떤 메트릭스가 컨텍스트와 관련이 있습니까? 어디에서 찾을 수 있습니까?

나는 그러나 이러한 프로파일은 내가 뒤에 이론뿐만 아니라 emperical 결과를 알고 싶습니다 (의 VTune XE) 비싸고 , 내가 을 프로파일 링하여이 문제를 피할 수있는 것을 알고있다.

또한 소프트웨어 프리 페치 (_mm_prefetch)에 신경을 써야합니까, 아니면 CPU가 나보다 나은 직업이라고 생각할 수 있습니까?

다음과 같은 기능이 있다고 가정 해 보겠습니다. 지침 중 일부를 삽 입해야합니까? 스트림 전에 상점을해야합니까, 모든로드를 순서대로 수행 한 다음 계산 등을할까요? USWC와 비 USWC를 비교할 필요가 있습니까?

  auto cur128  = reinterpret_cast<__m128i*>(cur); 
      auto prev128 = reinterpret_cast<const __m128i*>(prev); 
      auto dest128 = reinterpret_cast<__m128i*>(dest; 
      auto end  = cur128 + count/16; 

      while(cur128 != end)    
      { 
       auto xmm0 = _mm_add_epi8(_mm_load_si128(cur128+0), _mm_load_si128(prev128+0)); 
       auto xmm1 = _mm_add_epi8(_mm_load_si128(cur128+1), _mm_load_si128(prev128+1)); 
       auto xmm2 = _mm_add_epi8(_mm_load_si128(cur128+2), _mm_load_si128(prev128+2)); 
       auto xmm3 = _mm_add_epi8(_mm_load_si128(cur128+3), _mm_load_si128(prev128+3)); 

            // dest128 is USWC memory 
       _mm_stream_si128(dest128+0, xmm0); 
       _mm_stream_si128(dest128+1, xmm1); 
       _mm_stream_si128(dest128+2, xmm2);; 
       _mm_stream_si128(dest128+3, xmm3); 

            // cur128 is temporal, and will be used next time, which is why I choose store over stream 
       _mm_store_si128 (cur128+0, xmm0);    
       _mm_store_si128 (cur128+1, xmm1);     
       _mm_store_si128 (cur128+2, xmm2);     
       _mm_store_si128 (cur128+3, xmm3); 

       cur128 += 4; 
       dest128 += 4; 
       prev128 += 4; 
      } 

      std::swap(cur, prev); 
+1

나는 이것에 대한 해답이 측정 된 테스트에 있어야한다고 생각한다. x86은 꽤 오랫동안 [OOE] (http://en.wikipedia.org/wiki/Out-of-order_execution)를 가지고 있었지만 주문에 관계없이이 사건을 최적으로 처리 할 수 ​​있습니다. – Flexo

+0

테스트가 항상 최선입니다. 그러나이 경우에는 다소 비싼 프로파일 러가 필요합니다. VTune XE. 경험적 결과보다는 그 배경에 관한 이론에 대해 더 알고 싶습니다. OOE는 얼마나 멀리 떨어져 있습니까? 메모리 대기 시간이나 명령 대기 시간입니까? 다시 주문하면 OOE가 병렬로 실행할 수있는 명령을 처리합니까? – ronag

+0

릴리스 빌드 어셈블러 출력을 게시 할 수 있습니까? 컴파일러가 이것을 어떻게하는지 보는 것은 흥미로울 것입니다. – Skizz

답변

9

나는 테스트와 조정이 최선의 접근이라고 누구나 동의합니다. 그러나 그것을 돕기위한 트릭이 있습니다.

우선 MSVC 입니다. SSE 명령을 다시 주문하십시오. 귀하의 모범은 아마도 너무 단순하거나 이미 최적 일 것입니다.

일반적으로 말해서, 그렇게하기에 충분한 레지스터가 있다면 전체 인터리빙이 최상의 결과를 얻는 경향이 있습니다. 한 걸음 더 나아가려면 모든 레지스터를 사용할 수 있도록 루프를 풀고 너무 많이 엎 지르지 않도록합니다. 예제에서 루프는 메모리 액세스에 완전히 묶여 있기 때문에 더 좋은 여지는 없습니다.

대부분의 경우 최적의 성능을 얻으려면 지침의 순서를 완벽하게 이해할 필요가 없습니다. "충분히 가깝다"는 한 컴파일러 또는 하드웨어의 순서가 잘못된 실행이 문제를 해결할 것입니다.

내 코드가 최적인지 판단하는 데 사용하는 방법은 임계 경로 및 병목 현상 분석입니다. 루프를 작성한 후, 어떤 명령어가 어떤 리소스를 사용하는지 조회합니다. 이 정보를 사용하여 성능에 대한 상한선을 계산할 수 있으며 실제 결과와 비교하여 내가 최적 상태에 얼마나 가깝거나 먼지 확인할 수 있습니다.

예를 들어, 100 개의 덧셈과 50 개의 곱셈을 가진 루프가 있다고 가정합니다. Intel 및 AMD (Bulldozer 이전)에서 각 코어는 하나의 SSE/AVX add 및 SSE/AVX를 한 사이클에 여러 번 유지할 수 있습니다. 내 루프에 100 개가 추가되었으므로 100 사이클 이상을 수행 할 수 없다는 것을 알고 있습니다. 예, 배율기는 절반의 시간 동안 유휴 상태가되지만 합산자는 병목 현상입니다.

이제 루프를 실행하고 반복마다 105 사이클을 얻습니다. 그건 내가 최적에 가까워졌고 얻을 것이 많지 않다는 것을 의미합니다. 그러나 250 사이클을 얻으면 루프에 문제가 있다는 것을 의미합니다.

중요 경로 분석은 동일한 아이디어를 따릅니다. 모든 명령어의 대기 시간을 확인하고 루프의 중요한 경로의 사이클 시간을 찾으십시오. 실제 실적이 매우 근접한 경우 이미 최적입니다.

Agner 안개는 현재 프로세서의 내부 세부 사항에 대한 좋은 참조가 : http://www.agner.org/optimize/microarchitecture.pdf

나는 또한 인텔 ® 아키텍처 코드 분석기를 추천하고 싶은
6

난 그냥이 사용 VS2010 32 비트 컴파일러를 구축했고 나는 다음과 같은 얻을 : 컴파일러의 일반적인 규칙에 따라 지침을 재정렬되는 것을 보여줍니다

void F (void *cur, const void *prev, void *dest, int count) 
{ 
00901000 push  ebp 
00901001 mov   ebp,esp 
00901003 and   esp,0FFFFFFF8h 
    __m128i *cur128  = reinterpret_cast<__m128i*>(cur); 
00901006 mov   eax,220h 
0090100B jmp   F+10h (901010h) 
0090100D lea   ecx,[ecx] 
    const __m128i *prev128 = reinterpret_cast<const __m128i*>(prev); 
    __m128i *dest128 = reinterpret_cast<__m128i*>(dest); 
    __m128i *end  = cur128 + count/16; 

    while(cur128 != end)    
    { 
    auto xmm0 = _mm_add_epi8(_mm_load_si128(cur128+0), _mm_load_si128(prev128+0)); 
00901010 movdqa  xmm0,xmmword ptr [eax-220h] 
    auto xmm1 = _mm_add_epi8(_mm_load_si128(cur128+1), _mm_load_si128(prev128+1)); 
00901018 movdqa  xmm1,xmmword ptr [eax-210h] 
    auto xmm2 = _mm_add_epi8(_mm_load_si128(cur128+2), _mm_load_si128(prev128+2)); 
00901020 movdqa  xmm2,xmmword ptr [eax-200h] 
    auto xmm3 = _mm_add_epi8(_mm_load_si128(cur128+3), _mm_load_si128(prev128+3)); 
00901028 movdqa  xmm3,xmmword ptr [eax-1F0h] 
00901030 paddb  xmm0,xmmword ptr [eax-120h] 
00901038 paddb  xmm1,xmmword ptr [eax-110h] 
00901040 paddb  xmm2,xmmword ptr [eax-100h] 
00901048 paddb  xmm3,xmmword ptr [eax-0F0h] 

    // dest128 is USWC memory 
    _mm_stream_si128(dest128+0, xmm0); 
00901050 movntdq  xmmword ptr [eax-20h],xmm0 
    _mm_stream_si128(dest128+1, xmm1); 
00901055 movntdq  xmmword ptr [eax-10h],xmm1 
    _mm_stream_si128(dest128+2, xmm2);; 
0090105A movntdq  xmmword ptr [eax],xmm2 
    _mm_stream_si128(dest128+3, xmm3); 
0090105E movntdq  xmmword ptr [eax+10h],xmm3 

    // cur128 is temporal, and will be used next time, which is why I choose store over stream 
    _mm_store_si128 (cur128+0, xmm0);    
00901063 movdqa  xmmword ptr [eax-220h],xmm0 
    _mm_store_si128 (cur128+1, xmm1);     
0090106B movdqa  xmmword ptr [eax-210h],xmm1 
    _mm_store_si128 (cur128+2, xmm2);     
00901073 movdqa  xmmword ptr [eax-200h],xmm2 
    _mm_store_si128 (cur128+3, xmm3); 
0090107B movdqa  xmmword ptr [eax-1F0h],xmm3 

    cur128 += 4; 
00901083 add   eax,40h 
00901086 lea   ecx,[eax-220h] 
0090108C cmp   ecx,10h 
0090108F jne   F+10h (901010h) 
    dest128 += 4; 
    prev128 += 4; 
    } 
} 

"사용하지 않는 레지스터에 쓰기 직후의 레지스터 "라고합니다. 또한 두 개의로드 및 단일로드로의 추가 및 메모리에서의 추가로 바뀝니다. 이런 코드를 직접 쓰지 않고 현재 사용하고있는 4 개의 SIMD 레지스터 대신 모든 SIMD 레지스터를 사용할 이유가 없습니다. 로드 된 총 바이트 수를 캐시 라인의 크기와 일치시킬 수 있습니다. 이렇게하면 하드웨어 프리 페칭이 필요할 때까지 다음 캐시 라인을 채울 수있는 기회를줍니다.

또한 프리 페치는 특히 코드에서 메모리를 순차적으로 읽는 데 필요하지 않습니다. MMU는 한 번에 최대 4 개의 스트림을 프리 페치 할 수 있습니다.

1

: 그것은 정적

https://software.intel.com/en-us/articles/intel-architecture-code-analyzer

임계 경로, 대기 시간 및 처리량을 계산/최적화하는 데 도움이되는 코드 분석기입니다. Windows, Linux 및 MacOS에서 작동합니다 (Linux에서만 사용). 설명서에는 사용법에 대한 중간 정도의 간단한 예가 나와 있습니다 (즉, 지침을 다시 주문하여 지연 시간을 피하는 방법).

+0

꽤 좋지만 더 이상 유지되지 않습니다. 마지막으로 지원되는 마이크로 아키텍처는 Haswell입니다. Skylake를 튜닝 할 때 여전히 유용하지만 Intel에서 다시 업데이트하기를 바랍니다. 완벽하지 않고 많은 제약이 있으며 때로는 그 숫자가 실제 하드웨어와 일치하지 않는 경우도 있지만 확실히 유용합니다. –