2013-01-18 5 views
7

내 3D 계산을위한 최적화를 개발하고있어 지금이 :은과 같은 실행 파일에서 다른 최적화 (일반, SSE, AVX)가 C/C++

  • 표준 C를 사용하여 "plain"버전 프리 프로세서 #define USE_AVX
을 사용 때문에 컴파일 언어 라이브러리,
  • 처리기 #define USE_SSE를 사용하여 컴파일 SSE 최적화 된 버전,
  • AVX 최적화 된 버전

    다른 실행 파일을 컴파일하지 않고도 3 가지 버전간에 전환 할 수 있습니까 (예 : 서로 다른 라이브러리 파일을 가지고 있고 "올바른"로드를 동적으로로드하는 경우 inline 함수가 "올바르게"있는지 알 수 없습니까? 소프트웨어에서 이러한 종류의 스위치를 사용하는 경우에도 성능을 고려할 것입니다.

  • +1

    플랫폼에 대한 언급이 없습니까? 일부 플랫폼에서는 명령이 호출되지 않을지라도 avx를 사용하여 코드 실행을 거부합니다. 일부 플랫폼에는 런타임에 여러 구현 중에서 선택하는 ifunc가 있습니다. 일부 플랫폼은 기능에 의존하는 경로에서 공유 라이브러리를 찾습니다. –

    답변

    5

    한 가지 방법은 동일한 인터페이스를 준수하는 3 개의 라이브러리를 구현하는 것입니다. 동적 라이브러리를 사용하면 라이브러리 파일을 바꿀 수 있으며 실행 파일은 발견 한 내용을 사용합니다.

    • PlainImpl.dll
    • SSEImpl.dll
    • AVXImpl.dll

    을 그리고 Impl.dll에 대해 실행 링크를 만들 : Windows에서 예를 들어, 세 개의 DLL을 컴파일 할 수 있습니다. 이제 세 특정 DLL 중 하나를 .exe과 같은 디렉터리에 넣고 이름을 Impl.dll으로 변경하면 해당 버전이 사용됩니다. 동일한 원칙은 기본적으로 UNIX와 유사한 OS에 적용될 수 있습니다.

    다음 단계는 아마도 가장 유연성있는 프로그래밍 라이브러리를로드하는 것입니다,하지만 특정 OS이며, (함수 포인터 등을 획득, 라이브러리를 여는 등) 좀 더 작업이 필요

    편집 : 물론, 다른 매개 변수/설정 파일 설정 등에 따라 런타임에 함수를 세 번 구현하고 하나를 선택할 수도 있습니다.

    0

    물론 가능합니다.

    전체 작업을 수행하는 기능을 런타임에 수행 할 수있는 가장 좋은 방법입니다. 이 작동하지만, 최적이 아닌 것 : 각

    typedef enum 
    { 
        calc_type_invalid = 0, 
        calc_type_plain, 
        calc_type_sse, 
        calc_type_avx, 
        calc_type_max // not a valid value 
    } calc_type; 
    
    void do_my_calculation(float const *input, float *output, size_t len, calc_type ct) 
    { 
        float f; 
        size_t i; 
    
        for (i = 0; i < len; ++i) 
        { 
         switch (ct) 
         { 
          case calc_type_plain: 
           // plain calculation here 
           break; 
          case calc_type_sse: 
           // SSE calculation here 
           break; 
          case calc_type_avx: 
           // AVX calculation here 
           break; 
          default: 
           fprintf(stderr, "internal error, unexpected calc_type %d", ct); 
           exit(1); 
           break 
         } 
        } 
    } 
    

    코드가 단지 오버 헤드입니다 switch 문을 실행하고, 루프를 통해 전달합니다. 정말 똑똑한 컴파일러가 이론적으로이를 대신 할 수 있지만 스스로 해결하는 것이 좋습니다.

    대신 일반용, SSE 용 및 AVX 용의 세 가지 기능을 작성하십시오. 그런 다음 런타임에 어느 것을 실행할 지 결정하십시오.

    보너스 포인트를 얻으려면 "디버그"빌드에서 SSE와 일반 모두를 사용하여 계산을 수행하고 결과가 충분히 확신 할 수 있다고 주장하십시오. 속도가 아닌 평범한 버전을 작성하십시오. 그 결과를 사용하여 현명하게 최적화 된 버전이 올바른 답을 얻는 지 확인하십시오.

    전설적인 존 카맥 (John Carmack)은 후자의 접근 방식을 권장합니다. 그는 이것을 "병렬 구현"이라고 부릅니다. 그것에 대해 his essay을 읽으십시오.

    먼저 일반 버전을 작성하는 것이 좋습니다. 그런 다음 돌아가서 SSE 또는 AVX 가속을 사용하여 응용 프로그램의 일부를 다시 작성하고 가속 된 버전이 올바른 대답을 제공하는지 확인하십시오. (그리고 때로는 일반 버전에는 가속 버전이없는 버그가있을 수 있습니다. 두 버전을 가지고 비교하면 두 버전 모두에서 버그가 밝혀집니다.)

    +2

    최적화에 대해 생각하고 있다면, 루프 내에서 이러한 검사를하고 싶지 않을 것입니다. –

    +0

    예, 루프를 각 'switch'브랜치라는 함수 안에 넣는 편이 낫습니다. –

    +1

    또는 심지어 더 나은, 확장 및 3 최적화 ... polymorphic 스위치를 사용하여 구현 된 인터페이스 클래스가 있습니다. –

    6

    여러 가지 해결책이 있습니다.

    하나는 C + +를 기반으로 여러 클래스를 만듭니다. 일반적으로 인터페이스 클래스를 구현하고 팩토리 함수를 사용하여 올바른 클래스의 객체를 제공합니다.

    class Matrix 
    { 
        virtual void Multiply(Matrix &result, Matrix& a, Matrix &b) = 0; 
        ... 
    }; 
    
    class MatrixPlain : public Matrix 
    { 
        void Multiply(Matrix &result, Matrix& a, Matrix &b); 
    
    }; 
    
    
    void MatrixPlain::Multiply(...) 
    { 
        ... implementation goes here... 
    } 
    
    class MatrixSSE: public Matrix 
    { 
        void Multiply(Matrix &result, Matrix& a, Matrix &b); 
    } 
    
    void MatrixSSE::Multiply(...) 
    { 
        ... implementation goes here... 
    } 
    
    ... same thing for AVX... 
    
    Matrix* factory() 
    { 
        switch(type_of_math) 
        { 
         case PlainMath: 
          return new MatrixPlain; 
    
         case SSEMath: 
          return new MatrixSSE; 
    
         case AVXMath: 
          return new MatrixAVX; 
    
         default: 
          cerr << "Error, unknown type of math..." << endl; 
          return NULL; 
        } 
    } 
    

    또는 위의 제안, 당신은 공통 인터페이스를 가지고 있고, 동적으로 적합한 라이브러리를로드 공유 라이브러리를 사용할 수 있습니다.

    물론 "Matrix 기본 클래스를"일반 "클래스로 구현하면 단계별로 미세 조정을 수행하고 실제로 찾은 부분 만 구현하면 효과적이며 기본 클래스를 사용하여 성능이 ' 고도로 craticial.

    편집 : 당신은 인라인에 대해 이야기합니다. 나는 그런 경우에 잘못된 수준의 기능을보고 있다고 생각합니다. 꽤 많은 데이터를 처리하는 상당히 큰 기능을 원합니다. 그렇지 않으면 올바른 형식으로 데이터를 준비한 다음 몇 가지 계산 지침을 수행하고 데이터를 다시 메모리에 저장하는 데 모든 노력을 기울일 것입니다.

    데이터를 저장하는 방법에 대해서도 고려할 것입니다. X, Y, Z, W가있는 배열 집합을 저장하고 있습니까? 아니면 X를 많이, Y를 많이, Z를 많이, W를 많이 저장하고 있습니까? [3D 계산을한다고 가정 할 때]? 계산 방법에 따라 한 가지 방법이나 다른 방법을 사용하면 최상의 이점을 얻을 수 있습니다.

    나는 SSE와 3DNow를 꽤 잘 했어! 최적화는 몇 년 전의 일이며, "트릭"은 종종 데이터를 저장하는 방법에 대한 것이므로 한 번에 올바른 종류의 데이터 "묶음"을 쉽게 얻을 수 있습니다. 데이터를 잘못 저장하면 많은 시간을 낭비하게됩니다 (데이터를 한 가지 저장 방식에서 다른 방식으로 이동).

    +0

    단계별 구체화를위한 +1 –

    +0

    이 접근법의 문제점은 다른 아키텍처에 대해 다른 기능을 컴파일하고 최적화 할 수 없다는 점입니다. '-march = i7'로 컴파일하면 C 버전은 i7에서만 실행될 것이고'-march = i686'으로 컴파일하면 지난 15 년간 빌드 된 모든 머신에서 실행되지만 특정 내장 함수 (SSE/AVX와 같은)는 사용할 수 없으며 옵티마이 저는 SSE/AVX 버전에서 사용 가능한 명령어의 서브 세트 만 사용합니다. – hirschhornsalz

    +0

    그래서 별도의 소스 파일에 코드를 빌드하십시오.정말 좋은 방법으로 SSE/AVX 명령어를 실제로 사용하려면 인라인 어셈블러를 사용해야합니다. 컴파일러는 보통 "영리하다"라는 말을 잘하지 못합니다. –