2013-07-02 3 views
1

내가 vectorisation에 대해 배우려고 노력하고, 여기에 내 원래의 C++/STL 코드계산 평균

#include <vector> 
#include <vectorclass.h> 
template<typename T> 
double mean_v1(T begin,T end) { 
     float mean = 0; 
     std::for_each(begin,end,[&mean](const double& d) { mean+=d; }); 

    return mean/std::distance(begin,end); 
} 

double mean_v2(T begin,T end) { 
    float mean = 0; 
    const int distance = std::distance(begin,end); // This is expensive 
    const int loop = (distance >> 2)+1; // divide by 4 
    const int partial = distance & 2; // remainder 4 
    Vec4d vec; 
    for(int i = 0; i < loop;++i) { 
     if(i == (loop-1)) { 
      vec.load_partial(partial,&*begin); 
      mean = horizontal_add(vec); 
     } 
     else { 
      vec.load(&*begin); 
      mean = horizontal_add(vec); 
      begin+=4; // This is expensive 
     } 
    } 
    return mean/distance; 
} 

int main(int argc,char**argv) { 
    using namespace boost::assign; 
    std::vector<float> numbers; 
    // Note 13 numbers, which won't fit into a sse register perfectly 
    numbers+=39.57,39.57,39.604,39.58,39.61,31.669,31.669,31.669,31.65,32.09,33.54,32.46,33.45; 

    const float mean1 = mean_v1(numbers.begin(),numbers.end()); 
    const float mean2 = mean_v2(numbers.begin(),numbers.end()); 


    return 0; 
} 

두 V1의 내가 Agner Fog's vector library

을 사용하고 바퀴를 reinvet보다는 해요 및 v2는 올바르게 작동하며 둘 다 거의 같은 시간이 걸립니다. 그러나 그것을 프로파일 링하면 std :: distance()가 표시되고 반복기를 따라 이동하는 것은 전체 시간의 거의 45 %를 차지합니다. 벡터 추가는 0.8 %로 v1보다 훨씬 빠릅니다.

웹을 검색하면 모든 예제가 SSE 레지스터에 정확하게 들어 맞는 완벽한 수의 값을 처리하는 것처럼 보입니다. 예를 들어 루프를 설정하는 것이 계산보다 훨씬 오래 걸리는 경우와 같이 사람들이 홀수의 값을 처리하는 방법은 무엇입니까?

이 시나리오를 처리하는 방법에 대한 모범 사례 또는 아이디어가 있어야한다고 생각합니다.

나는 [] 부동 취할 평균()의 인터페이스를 변경할 수는 없지만 반복자 당신이 누적 더블하자하지 않습니다 특히 당신은 이중 불필요 플로트 &을 혼합하고

+0

충고와 마찬가지로 : 아마도 추력 라이브러리가 당신에게 흥미로울 것입니다. https://code.google.com/p/thrust/ – eraxillan

+0

실제로 흥미로운 것 같습니다 – Ronnie

답변

3

를 사용해야합니다 가정 당신의 정밀도는 완전히 파괴되고 더 큰 계열에 대해서는 만족스럽지 않을 것입니다.

산술 연산이 매우 가볍기 때문에 실적을 파괴하는 것은 메모리 액세스 가능성이 가장 높으며 메모리 캐시 라인 및 그 작동 방식을 읽는 것입니다. 기본적으로 여기서해야 할 일은 미리 조사하는 것입니다. 일부 프로세서는 캐시에 물건을 가져 오기위한 명시적인 지침이 있습니다. 그렇지 않으면 미리 메모리 위치에서로드를 수행 할 수 있습니다. 루프에서 일정한 간격으로 다른 레벨의 중첩을 만들면 몇 번의 반복 작업으로 얻을 수있는 데이터로 캐시를 준비 할 수 있습니다.

사람들이 성능을 최대화하기 위해 수행하는 작업은 실제로 데이터 레이아웃을 디자인하는 데 많은 시간을 소비한다는 것입니다. 데이터에 중간 변환을 할 필요가 없습니다. 사람들이하는 일은 정렬 된 메모리를 할당하는 것입니다 (대부분의 SIMD 명령어 세트는 정렬되지 않은 메모리에 대한 읽기/쓰기에 중대한 처벌을 요구하거나 부과합니다). 그런 다음 명령어 세트에 맞는 방식으로 데이터를 집계하려고합니다. 사실 명령어 세트가 지원하는 레지스터 크기에 상관없이 데이터를 채우는 것이 종종 좋은 방법입니다. 따라서 3 차원 벡터를 처리하려고하면 사용되지 않는 추가 요소로 채우기가 거의 항상 큰 승리가됩니다.

+0

+1 최적화에 대한 일반적인 조언은 –

+0

입니다. 매우 유용한 지침; 그러나 그것은 알고리즘이 최적화에 맞게 호출되는 방식을 재구성해야한다는 것을 의미합니다. 이 경우 더 큰 알고리즘 거래 코드의 일부입니다. – Ronnie

+0

@Ylisar, 추가 요소를 패딩하여 하나의 SSE 또는 AVX 레지스터로 3D 벡터를 처리하는 것은 분명히 원하는 작업이 아닙니다. 너무 많은 사람들이 SIMD로 이러한 실수를 저지르고 있습니다. SIMD를 사용하는 올바른 방법은 스칼라 코드와 유사합니다. 차이점은 다음과 같습니다. 3 개의 x, y, z 스칼라 레지스터를 사용하는 대신에 3 개의 SIMD 레지스터 (vx = xxxx, vy = yyyy, vz = zzzz)를 사용하고 SIMD 너비와 동일한 수의 벡터를 동시에 연산합니다. 이것은 100 % 효율을 얻고 수평 지시를 사용하지 않아도됩니다. –