문제

파일 (64 채널 웨이브 파일을 간격하게하는 8 비트 데이터)에 대한 산란 된 읽기를 수행하고 있습니다. 그런 다음 그것들을 결합하여 단일 바이트 스트림이됩니다. 내가 가진 문제는 데이터를 재구성하는 것입니다.

기본적으로 16 바이트로 읽은 다음 단일 __m128i 변수로 빌드 한 다음 _mm_stream_ps를 사용하여 값을 메모리에 다시 작성합니다. 그러나 이상한 성능 결과가 있습니다.

첫 번째 계획에서 나는 _mm_set_epi8 내재를 사용하여 다음과 같이 __m128i를 설정합니다.

    const __m128i packedSamples = _mm_set_epi8( sample15,   sample14,   sample13,   sample12,   sample11,   sample10,   sample9,    sample8,
                                                sample7,    sample6,    sample5,    sample4,    sample3,    sample2,    sample1,    sample0 );

기본적으로 나는 최고의 성능을 제공하기 위해 최적화하는 방법을 결정하기 위해 컴파일러에 모든 것을 남겨 둡니다. 이것은 최악의 성능을 제공합니다. 내 테스트는 ~ 0.195 초 안에 실행됩니다.

둘째, 4 _mm_set_epi32 지침을 사용하여 통합하려고 시도한 다음 아래로 포장했습니다.

    const __m128i samples0      = _mm_set_epi32( sample3, sample2, sample1, sample0 );
    const __m128i samples1      = _mm_set_epi32( sample7, sample6, sample5, sample4 );
    const __m128i samples2      = _mm_set_epi32( sample11, sample10, sample9, sample8 );
    const __m128i samples3      = _mm_set_epi32( sample15, sample14, sample13, sample12 );

    const __m128i packedSamples0    = _mm_packs_epi32( samples0, samples1 );
    const __m128i packedSamples1    = _mm_packs_epi32( samples2, samples3 );
    const __m128i packedSamples     = _mm_packus_epi16( packedSamples0, packedSamples1 );

이것은 성능을 다소 향상시킵니다. 내 테스트는 이제 ~ 0.15 초 안에 실행됩니다. 이것이 바로 _mm_set_epi8이 어쨌든하고있는 일이라고 생각하기 때문에 성능이 향상 될 것이라는 반 직관적 인 것 같습니다 ... 어쨌든 ...

나의 마지막 시도는 4 개의 CC를 구식 방식 (교대 및 ORS 포함)으로 만들고 단일 _MM_SET_EPI32를 사용하여 __M128I에 넣는 데있어 약간의 코드를 사용하는 것이 었습니다.

    const GCui32 samples0       = MakeFourCC( sample0, sample1, sample2, sample3 );
    const GCui32 samples1       = MakeFourCC( sample4, sample5, sample6, sample7 );
    const GCui32 samples2       = MakeFourCC( sample8, sample9, sample10, sample11 );
    const GCui32 samples3       = MakeFourCC( sample12, sample13, sample14, sample15 );
    const __m128i packedSamples = _mm_set_epi32( samples3, samples2, samples1, samples0 );

이것은 더 나은 성능을 제공합니다. 테스트를 실행하는 데 ~ 0.135 초가 걸립니다. 나는 정말로 혼란스러워지기 시작했다.

그래서 간단한 읽기 바이트 작성 바이트 시스템을 시도했는데 마지막 방법보다 훨씬 빠릅니다.

그래서 무슨 일이 일어나고 있습니까? 이 모든 것이 나에게 반 직관적 인 것 같습니다.

데이터를 너무 빨리 공급하기 때문에 _MM_STREAM_PS에서 지연이 발생한다는 생각을 고려했지만 내가하는 일을 정확히 동일한 결과를 얻을 수 있습니다. 처음 2 가지 방법이 16 하중이 루프를 통해 분산되어 대기 시간을 숨길 수 없다는 것을 의미 할 수 있습니까? 그렇다면 왜 이거야? 반드시 본질적으로 컴파일러는 최적화를 즐겁게하는 곳을 만들 수 있습니다. 나는 그것이 요점이라고 생각했습니다 ... 또한 16 개의 읽기와 16 개의 글을 16 개의 읽기보다 훨씬 느리게하고 16 개의 쓰기는 SSE 저글링으로 16 개의 글을 쓸 것입니다. 지시 사항 ... 모든 읽기와 쓰기 후에는 느린 비트입니다!

무슨 일이 일어나고 있는지 아이디어를 가진 사람이라면 누구나 감사하겠습니다! :디

편집 : 아래의 주석에 대해서는 바이트를 상수로 사전로드하는 것을 중지하고 다음과 같이 변경되었습니다.

    const __m128i samples0      = _mm_set_epi32( *(pSamples + channelStep3), *(pSamples + channelStep2), *(pSamples + channelStep1), *(pSamples + channelStep0) );
    pSamples    += channelStep4;
    const __m128i samples1      = _mm_set_epi32( *(pSamples + channelStep3), *(pSamples + channelStep2), *(pSamples + channelStep1), *(pSamples + channelStep0) );
    pSamples    += channelStep4;
    const __m128i samples2      = _mm_set_epi32( *(pSamples + channelStep3), *(pSamples + channelStep2), *(pSamples + channelStep1), *(pSamples + channelStep0) );
    pSamples    += channelStep4;
    const __m128i samples3      = _mm_set_epi32( *(pSamples + channelStep3), *(pSamples + channelStep2), *(pSamples + channelStep1), *(pSamples + channelStep0) );
    pSamples    += channelStep4;

    const __m128i packedSamples0    = _mm_packs_epi32( samples0, samples1 );
    const __m128i packedSamples1    = _mm_packs_epi32( samples2, samples3 );
    const __m128i packedSamples     = _mm_packus_epi16( packedSamples0, packedSamples1 );

그리고 이것은 성능이 ~ 0.143 초로 향상되었습니다. Sitll은 스트레이트 C 구현만큼 좋지 않습니다 ...

다시 편집 : 지금까지 얻는 최고의 성능은

    // Load the samples.
    const GCui8 sample0     = *(pSamples + channelStep0);
    const GCui8 sample1     = *(pSamples + channelStep1);
    const GCui8 sample2     = *(pSamples + channelStep2);
    const GCui8 sample3     = *(pSamples + channelStep3);

    const GCui32 samples0   = Build32( sample0, sample1, sample2, sample3 );
    pSamples += channelStep4;

    const GCui8 sample4     = *(pSamples + channelStep0);
    const GCui8 sample5     = *(pSamples + channelStep1);
    const GCui8 sample6     = *(pSamples + channelStep2);
    const GCui8 sample7     = *(pSamples + channelStep3);

    const GCui32 samples1   = Build32( sample4, sample5, sample6, sample7 );
    pSamples += channelStep4;

    // Load the samples.
    const GCui8 sample8     = *(pSamples + channelStep0);
    const GCui8 sample9     = *(pSamples + channelStep1);
    const GCui8 sample10    = *(pSamples + channelStep2);
    const GCui8 sample11    = *(pSamples + channelStep3);

    const GCui32 samples2       = Build32( sample8, sample9, sample10, sample11 );
    pSamples += channelStep4;

    const GCui8 sample12    = *(pSamples + channelStep0);
    const GCui8 sample13    = *(pSamples + channelStep1);
    const GCui8 sample14    = *(pSamples + channelStep2);
    const GCui8 sample15    = *(pSamples + channelStep3);

    const GCui32 samples3   = Build32( sample12, sample13, sample14, sample15 );
    pSamples += channelStep4;

    const __m128i packedSamples = _mm_set_epi32( samples3, samples2, samples1, samples0 );

    _mm_stream_ps( pWrite + 0,  *(__m128*)&packedSamples ); 

이것은 ~ 0.095 초 안에 처리하여 상당히 더 나은 처리를 제공합니다. 나는 SSE와 가까워 질 수없는 것처럼 보이지 않는다 ... 나는 여전히 그것에 의해 혼란스러워하지만 .. Ho Hum.

도움이 되었습니까?

해결책

아마도 컴파일러는 모든 인수를 고유의 인수에 한 번에 레지스터에 넣으려고 할 것입니다. 정리하지 않고 한 번에 많은 변수에 액세스하고 싶지 않습니다.

각 샘플에 대해 별도의 식별자를 선언하는 대신 char[16]. 컴파일러는 배열 내에서 어떤 주소를 가져 가지 않는 한 적합도가 보이면 16 값을 레지스터로 홍보합니다. 당신은 추가 할 수 있습니다 __aligned__ 태그 (또는 vc ++가 사용하는 것이 무엇이든) 고유 한 것을 피할 수 있습니다. 그렇지 않으면, 내재적이라고 부릅니다 ( sample[15], sample[14], sample[13] … sample[0] ) 컴파일러의 작업을 더 쉽게 만들거나 최소한 해를 끼치 지 않아야합니다.


편집하다: 나는 당신이 레지스터 유출과 싸우고 있다고 확신하지만 그 제안은 아마도 바이트를 개별적으로 저장할 것입니다. 제 조언은 최종 시도 (MakeFourCC 사용)를 읽기 작업과 인터 리브하여 올바르게 예약되어 있고 스택으로의 왕복이 없는지 확인하는 것입니다. 물론 객체 코드 검사가이를 보장하는 가장 좋은 방법입니다.

기본적으로 데이터를 레지스터 파일로 스트리밍 한 다음 다시 스트리밍합니다. 데이터를 플러시하기 전에 과부하를 원하지 않습니다.

다른 팁

VS는 고유를 최적화하는 데 악명 높다. 특히 데이터를 SSE 레지스터에서 이동합니다. 내재 자체는 꽤 잘 사용됩니다 ....

당신이 보는 것은이 몬스터로 SSE 레지스터를 채우려는 것입니다.

00AA100C  movzx       ecx,byte ptr [esp+0Fh]  
00AA1011  movzx       edx,byte ptr [esp+0Fh]  
00AA1016  movzx       eax,byte ptr [esp+0Fh]  
00AA101B  movd        xmm0,eax  
00AA101F  movzx       eax,byte ptr [esp+0Fh]  
00AA1024  movd        xmm2,edx  
00AA1028  movzx       edx,byte ptr [esp+0Fh]  
00AA102D  movd        xmm1,ecx  
00AA1031  movzx       ecx,byte ptr [esp+0Fh]  
00AA1036  movd        xmm4,ecx  
00AA103A  movzx       ecx,byte ptr [esp+0Fh]  
00AA103F  movd        xmm5,edx  
00AA1043  movzx       edx,byte ptr [esp+0Fh]  
00AA1048  movd        xmm3,eax  
00AA104C  movzx       eax,byte ptr [esp+0Fh]  
00AA1051  movdqa      xmmword ptr [esp+60h],xmm0  
00AA1057  movd        xmm0,edx  
00AA105B  movzx       edx,byte ptr [esp+0Fh]  
00AA1060  movd        xmm6,eax  
00AA1064  movzx       eax,byte ptr [esp+0Fh]  
00AA1069  movd        xmm7,ecx  
00AA106D  movzx       ecx,byte ptr [esp+0Fh]  
00AA1072  movdqa      xmmword ptr [esp+20h],xmm4  
00AA1078  movdqa      xmmword ptr [esp+80h],xmm0  
00AA1081  movd        xmm4,ecx  
00AA1085  movzx       ecx,byte ptr [esp+0Fh]  
00AA108A  movdqa      xmmword ptr [esp+70h],xmm2  
00AA1090  movd        xmm0,eax  
00AA1094  movzx       eax,byte ptr [esp+0Fh]  
00AA1099  movdqa      xmmword ptr [esp+10h],xmm4  
00AA109F  movdqa      xmmword ptr [esp+50h],xmm6  
00AA10A5  movd        xmm2,edx  
00AA10A9  movzx       edx,byte ptr [esp+0Fh]  
00AA10AE  movd        xmm4,eax  
00AA10B2  movzx       eax,byte ptr [esp+0Fh]  
00AA10B7  movd        xmm6,edx  
00AA10BB  punpcklbw   xmm0,xmm1  
00AA10BF  punpcklbw   xmm2,xmm3  
00AA10C3  movdqa      xmm3,xmmword ptr [esp+80h]  
00AA10CC  movdqa      xmmword ptr [esp+40h],xmm4  
00AA10D2  movd        xmm4,ecx  
00AA10D6  movdqa      xmmword ptr [esp+30h],xmm6  
00AA10DC  movdqa      xmm1,xmmword ptr [esp+30h]  
00AA10E2  movd        xmm6,eax  
00AA10E6  punpcklbw   xmm4,xmm5  
00AA10EA  punpcklbw   xmm4,xmm0  
00AA10EE  movdqa      xmm0,xmmword ptr [esp+50h]  
00AA10F4  punpcklbw   xmm1,xmm0  
00AA10F8  movdqa      xmm0,xmmword ptr [esp+70h]  
00AA10FE  punpcklbw   xmm6,xmm7  
00AA1102  punpcklbw   xmm6,xmm2  
00AA1106  movdqa      xmm2,xmmword ptr [esp+10h]  
00AA110C  punpcklbw   xmm2,xmm0  
00AA1110  movdqa      xmm0,xmmword ptr [esp+20h]  
00AA1116  punpcklbw   xmm1,xmm2  
00AA111A  movdqa      xmm2,xmmword ptr [esp+40h]  
00AA1120  punpcklbw   xmm2,xmm0  
00AA1124  movdqa      xmm0,xmmword ptr [esp+60h]  
00AA112A  punpcklbw   xmm3,xmm0  
00AA112E  punpcklbw   xmm2,xmm3  
00AA1132  punpcklbw   xmm6,xmm4  
00AA1136  punpcklbw   xmm1,xmm2  
00AA113A  punpcklbw   xmm6,xmm1  

이것은 훨씬 더 잘 작동하고 쉽게 더 빠르게해야합니다.

__declspec(align(16)) BYTE arr[16] = { sample15, sample14, sample13, sample12, sample11, sample10, sample9, sample8, sample7, sample6, sample5, sample4, sample3, sample2, sample1, sample0 };

__m128i packedSamples = _mm_load_si128( (__m128i*)arr );

내 자신의 시험 침대 구축 :

void    f()
{
    const int steps = 1000000;
    BYTE* pDest = new BYTE[steps*16+16];
    pDest += 16 - ((ULONG_PTR)pDest % 16);
    BYTE* pSrc = new BYTE[steps*16*16];

    const int channelStep0 = 0;
    const int channelStep1 = 1;
    const int channelStep2 = 2;
    const int channelStep3 = 3;
    const int channelStep4 = 16;

    __int64 freq;
    QueryPerformanceFrequency( (LARGE_INTEGER*)&freq );
    __int64 start = 0, end;
    QueryPerformanceCounter( (LARGE_INTEGER*)&start );

    for( int step = 0; step < steps; ++step )
    {
        __declspec(align(16)) BYTE arr[16];
        for( int j = 0; j < 4; ++j )
        {
            //for( int i = 0; i < 4; ++i )
            {
                arr[0+j*4] = *(pSrc + channelStep0);
                arr[1+j*4] = *(pSrc + channelStep1);
                arr[2+j*4] = *(pSrc + channelStep2);
                arr[3+j*4] = *(pSrc + channelStep3);
            }
            pSrc += channelStep4;
        }

#if test1
// test 1 with C
        for( int i = 0; i < 16; ++i )
        {
            *(pDest + step * 16 + i) = arr[i];
        }
#else
// test 2 with SSE load/store    
        __m128i packedSamples = _mm_load_si128( (__m128i*)arr );
        _mm_stream_si128( ((__m128i*)pDest) + step, packedSamples );
#endif
    }

    QueryPerformanceCounter( (LARGE_INTEGER*)&end );

    printf( "%I64d", (end - start) * 1000 / freq );

}

나에게 테스트 2가 더 빠릅니다. 그런 다음 테스트 1.

내가 뭔가 잘못 했나요? 이것은 당신이 사용하는 코드가 아닌가? 내가 무엇을 그리워합니까? 이게 나만을위한 것인가?

내입을 사용하면 컴파일러 최적화가 나뉩니다!

고유 함수의 요점은 컴파일러가 컴파일러가 알고있는 OPCODE 스트림에 모르는 OPCODES를 삽입하는 것입니다. 컴파일러에 Opcode에 대한 메타 데이터와 레지스터 및 메모리에 어떤 영향을 미치는지에 대한 메타 데이터가 제공되지 않는 한, 컴파일러는 고유를 실행 한 후에는 데이터가 보존된다고 가정 할 수 없습니다. 이것은 실제로 컴파일러의 최적화 부분을 아프게합니다. 고유의 지시 사항을 재정렬 할 수 없으며 레지스터가 영향을받지 않는다고 가정 할 수 없습니다.

이것을 최적화하는 가장 좋은 방법은 더 큰 그림을 보는 것입니다. 소스 데이터를 읽는 것부터 최종 출력 작성에 이르기까지 전체 프로세스를 고려해야합니다. 마이크로 최적화는 실제로 시작하기 위해 실제로 나쁜 일을하지 않는 한 큰 결과를 제공하지 않습니다.

아마도 필요한 입력을 자세히 설명하고 여기에 누군가가 처리 할 최적의 방법을 제안 할 수 있습니다.

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