문제

큰 정수 배열의 각 요소에 대해 실행되는 C++로 작성된 루프가 있습니다.루프 내에서 정수의 일부 비트를 마스크한 다음 최소값과 최대값을 찾습니다.이러한 작업에 SSE 명령어를 사용하면 비트 AND 및 if-else 조건을 사용하여 작성된 일반 루프에 비해 훨씬 빠르게 실행된다고 들었습니다.제 질문은 이 SSE 지침을 따라야 합니까?입니다.또한 내 코드가 다른 프로세서에서 실행되면 어떻게 되나요?여전히 작동합니까? 아니면 이 지침이 프로세서별로 적용됩니까?

도움이 되었습니까?

해결책

  1. SSE 명령어는 프로세서에 따라 다릅니다. Wikipedia의 어떤 SSE 버전을 지원하는 프로세서를 찾을 수 있습니다.
  2. SSE 코드가 더 빠르거나 많은 요인에 의존하는 경우 : 첫 번째는 물론 문제가 메모리 바운드인지 CPU 바운드인지 여부입니다. 메모리 버스가 있다면 병목 현상 SSE가 많이 도움이되지 않습니다. 정수 계산을 단순화하십시오. 코드가 더 빨라지면 CPU에 묶여있을 수 있으며 속도를 높일 가능성이 높습니다.
  3. Simd-Code를 작성하는 것이 C ++ 코드를 작성하는 것보다 훨씬 어렵고 결과 코드를 변경하기가 훨씬 어렵다는 점에 유의하십시오. 항상 C ++ 코드를 최신 상태로 유지하면 주석으로 원하고 어셈블러 코드의 정확성을 확인하십시오.
  4. 다양한 프로세서에 최적화 된 일반적인 저수준 SIMD 작업을 구현하는 IPP와 같은 라이브러리를 사용하는 방법을 생각해보십시오.

다른 팁

SSE 중 SIMD는 여러 데이터 덩어리에서 동일한 작업을 수행 할 수 있습니다. 따라서 정수 작업을위한 직접 대체품으로 SSE를 사용하는 데 어떤 유리도 이점을 얻지 못하므로 한 번에 여러 데이터 항목에서 작업을 수행 할 수있는 경우에만 이점을 얻을 수 있습니다. 여기에는 메모리가 인접한 일부 데이터 값을로드하고 필요한 처리를 수행 한 다음 배열의 다음 값 세트로 밟아야합니다.

문제 :

1 코드 경로가 처리중인 데이터에 의존하는 경우 SIMD는 구현하기가 훨씬 어려워집니다. 예를 들어:

a = array [index];
a &= mask;
a >>= shift;
if (a < somevalue)
{
  a += 2;
  array [index] = a;
}
++index;

SIMD로 쉽게 수행 할 수 없습니다.

a1 = array [index] a2 = array [index+1] a3 = array [index+2] a4 = array [index+3]
a1 &= mask         a2 &= mask           a3 &= mask           a4 &= mask
a1 >>= shift       a2 >>= shift         a3 >>= shift         a4 >>= shift
if (a1<somevalue)  if (a2<somevalue)    if (a3<somevalue)    if (a4<somevalue)
  // help! can't conditionally perform this on each column, all columns must do the same thing
index += 4

2 데이터가 연속적이지 않은 경우 데이터를 SIMD 명령에로드하는 것이 번거 롭습니다.

3 코드는 프로세서에 따라 다릅니다. SSE는 IA32 (Intel/AMD)에만 있으며 모든 IA32 CPUS 지원 SSE는 아닙니다.

알고리즘과 데이터를 분석하여 SSE가 될 수 있는지 확인하고 SSE의 작동 방식을 알아야합니다. 인텔 웹 사이트에는 많은 문서가 있습니다.

이러한 종류의 문제는 좋은 저수준 프로파일 러가 필수적인 곳의 완벽한 예입니다. (Vtune과 같은 것) 그것은 당신의 핫스팟이 어디에 있는지에 대한 훨씬 더 많은 정보를 제공 할 수 있습니다.

내 생각에, 당신이 묘사 한 바에 따르면, 당신의 핫스팟은 아마도 if/else를 사용하여 Min/Max 계산으로 인한 지점 예측 실패 일 것입니다. 따라서 SIMD Intrinsics를 사용하면 Min/Max 지침을 사용할 수 있지만 대신 Branchless Min/Max Caluculation을 사용하는 것이 좋습니다. 이것은 고통이 적은 대부분의 이익을 달성 할 수 있습니다.

이 같은:

inline int 
minimum(int a, int b)
{
  int mask = (a - b) >> 31;
  return ((a & mask) | (b & ~mask));
}

SSE 지침을 사용하는 경우이를 지원하는 프로세서로 제한됩니다. 그것은 x86을 의미합니다. 펜티엄 2 정도로 거슬러 올라갑니다 (소개 된시기를 정확히 기억할 수 없지만 오래 전에).

내가 기억할 수있는 한 정수 작업을 제공하는 SSE2는 다소 최근입니다 (Pentium 3? 비록 첫 번째 AMD Athlon 프로세서가 지원하지 않았지만)

어쨌든이 지침을 사용하는 두 가지 옵션이 있습니다. 전체 코드 블록을 어셈블리에 작성하십시오 (아마도 나쁜 아이디어 일 것입니다. 컴파일러가 코드를 최적화하는 것을 사실상 불가능하게 만들고 인간이 효율적인 어셈블러를 작성하기가 매우 어렵습니다).

또는 컴파일러와 함께 사용할 수있는 본질을 사용하십시오 (메모리가 제공되는 경우 일반적으로 xmmintrin.h에 정의됩니다).

그러나 다시 성능이 향상되지 않을 수 있습니다. SSE 코드는 처리하는 데이터의 추가 요구 사항을 제시합니다. 주로 명심해야 할 것은 데이터가 128 비트 경계에 정렬되어야한다는 것입니다. 동일한 레지스터에로드 된 값 사이에 종속성이 거의 없거나 전혀 없어야합니다 (128 비트 SSE 레지스터는 4 개의 int를 보유 할 수 있습니다. 첫 번째와 두 번째를 함께 추가하는 것은 최적이 아닙니다. 그러나 4 개의 int를 해당 4 개의 int에 추가하는 것은 모두 다른 레지스터는 빠릅니다)

모든 저급 SSE 피들링을 감싸는 라이브러리를 사용하고 싶을 수도 있지만 잠재적 인 성능 이점을 망칠 수도 있습니다.

SSE의 정수 운영 지원이 얼마나 좋은지 모르겠으므로 성능을 제한 할 수있는 요소 일 수도 있습니다. SSE는 주로 플로팅 포인트 작업 속도를 높이는 대상입니다.

Microsoft Visual C ++를 사용하려는 경우 다음을 읽어야합니다.

http://www.codeproject.com/kb/recipes/sseintro.aspx

우리는 당신이 설명하는 것과 유사하지만 바이트 배열에서 SSE에서 일부 이미지 처리 코드를 구현했습니다. 인텔 컴파일러와 관련하여 정확한 알고리즘에 따라 C 코드와 비교 한 속도가 상당합니다. 그러나 이미 언급했듯이 다음 단점이 있습니다.

  • 이식성. 코드는 모든 인텔과 같은 CPU에서 실행되므로 AMD에서도 실행되지만 다른 CPU는 아닙니다. 대상 하드웨어를 제어하기 때문에 그것은 우리에게는 문제가되지 않습니다. 컴파일러를 64 비트 OS로 전환하는 것도 문제가 될 수 있습니다.

  • 당신은 가파른 학습 곡선이 있지만, 새로운 알고리즘을 작성하는 원칙을 이해 한 후에는 그렇게 어렵지 않다는 것을 알았습니다.

  • 유지 가능성. 대부분의 C 또는 C ++ 프로그래머는 어셈블리/SSE에 대한 지식이 없습니다.

내 조언은 성능 향상이 필요한 경우에만이를 위해 가야하며 인텔 IPP와 같은 라이브러리에서 문제에 대한 기능을 찾을 수없고 휴대 성 문제를 가지고 살 수있는 경우에도 갈 것입니다.

SSE가 일반 C 버전의 코드 (인라인 ASM 없음, 본질이 사용되지 않음)에 대한 거대한 (4 배 이상) 속도를 제공하지만 컴파일러가 할 수있는 경우 직접 최적화 된 어셈블러가 컴파일러 생성 어셈블리를 이길 수 있다고 경험을 통해 알 수 있습니다. t 프로그래머가 의도 한 바를 알아냅니다 (컴파일러는 가능한 모든 코드 조합을 다루지 않으며 절대로 절대하지 않습니다). 아, 그리고 컴파일러는 매번 가장 빠른 속도로 실행되는 데이터를 레이아웃 할 수 없습니다. 그러나 인텔 컴파일러에 대한 속도를 높이기 위해 많은 경험이 필요합니다 (가능한 경우).

SSE 지침은 원래 Intel Chips에 있었지만 최근 (Athlon? 이후로) AMD도 지원하므로 SSE 명령 세트에 대해 코드를 수행하면 대부분의 X86 Procs에 휴대 할 수 있어야합니다.

즉, X86에서 Assembler에 이미 익숙하지 않으면 SSE 코딩을 배우는 데 시간이 가치가 없을 수도 있습니다. 더 쉬운 옵션은 컴파일러 문서를 확인하고 컴파일러가 SSE 코드를 자동 생성 할 수있는 옵션이 있는지 확인하는 것입니다. 당신을 위한. 일부 컴파일러는 이러한 방식으로 루프를 매우 잘 벡터화합니다. (인텔 컴파일러가 이것을 잘한다는 소식을 듣고 놀라지 않을 것입니다. :)

컴파일러가 자신이하는 일을 이해하는 데 도움이되는 코드를 작성하십시오. GCC는 다음과 같은 SSE 코드를 이해하고 최적화합니다.

typedef union Vector4f
{
        // Easy constructor, defaulted to black/0 vector
    Vector4f(float a = 0, float b = 0, float c = 0, float d = 1.0f):
        X(a), Y(b), Z(c), W(d) { }

        // Cast operator, for []
    inline operator float* ()
    { 
        return (float*)this;
    }

        // Const ast operator, for const []
    inline operator const float* () const
    { 
        return (const float*)this;
    }

    // ---------------------------------------- //

    inline Vector4f operator += (const Vector4f &v)
    {
        for(int i=0; i<4; ++i)
            (*this)[i] += v[i];

        return *this;
    }

    inline Vector4f operator += (float t)
    {
        for(int i=0; i<4; ++i)
            (*this)[i] += t;

        return *this;
    }

        // Vertex / Vector 
        // Lower case xyzw components
    struct {
        float x, y, z;
        float w;
    };

        // Upper case XYZW components
    struct {
        float X, Y, Z;
        float W;
    };
};

빌드 매개 변수에 -msse -msse2가 있다는 것을 잊지 마십시오!

SSE가 일부 프로세서에만 특이 적이지만 (SSE는 비교적 안전하고, 내 경험에서 SSE2가 훨씬 적음) 런타임시 CPU를 감지하고 대상 CPU에 따라 동적으로 코드를로드 할 수 있습니다.

SIMD Intrinsics (예 : SSE2)는 이런 종류의 속도를 높일 수 있지만 전문 지식을 사용하여 올바르게 사용합니다. 그것들은 정렬 및 파이프 라인 대기 시간에 매우 민감합니다. 부주의 한 사용은 성능이없는 것보다 훨씬 악화 될 수 있습니다. 캐시 프리 페치를 사용하여 모든 INT가 L1에 있는지 확인하기 위해 훨씬 쉽고 즉각적인 속도를 얻을 수 있습니다.

기능이 초당 100,000,000 정수보다 더 나은 처리량이 필요하지 않으면 Simd는 아마도 당신에게 문제가되지 않을 것입니다.

다른 CPU에서 사용할 수있는 다른 SSE 버전에 대해 이전에 언급 한 내용을 간단히 추가하려면 CPUID 명령에 의해 반환 된 각 기능 플래그를 살펴보면 확인할 수 있습니다 (자세한 내용은 EG Intel의 문서 참조).

살펴보십시오 인라인 어셈블러 C/C ++의 경우 다음은 다음과 같습니다 DDJ 기사. 귀하의 프로그램이 호환 가능한 플랫폼에서 실행될 것이라고 확신하지 않는 한, 많은 사람들이 여기에 제공 한 권장 사항을 따라야합니다.

나는 이전 포스터에 동의합니다. 혜택은 상당히 클 수 있지만 얻으려면 많은 작업이 필요할 수 있습니다. 이 지침에 대한 인텔 문서는 4K 페이지가 넘습니다. Ocali Inc.에서 무료 Easysse (Intrinsics+ 예제를 통해 C ++ 포장지 라이브러리)를 확인할 수 있습니다.

이 Easysse와의 제휴가 명확하다고 가정합니다.

조립에 상당히 능숙하지 않다면 직접 이 작업을 수행하지 않는 것이 좋습니다.SSE를 사용하려면 데이터를 신중하게 재구성해야 할 가능성이 높습니다. 스키즈 지적하며 그 이점이 기껏해야 의심스러운 경우가 많습니다.

매우 작은 루프를 작성하고 데이터를 매우 긴밀하게 구성한 다음 이 작업을 수행하는 컴파일러에 의존하는 것이 훨씬 더 나을 것입니다.Intel C 컴파일러와 GCC(4.1 이후)는 모두 코드를 자동 벡터화할 수 있으며 아마도 여러분보다 더 나은 작업을 수행할 것입니다.(CXXFLAGS에 -ftree-Vectorize를 추가하기만 하면 됩니다.)

편집하다:제가 언급해야 할 또 다른 점은 여러 컴파일러가 다음을 지원한다는 것입니다. 어셈블리 내장, 아마도 IMO는 asm() 또는 __asm{} 구문보다 사용하기 더 쉬울 것입니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top