SSE-copy, AVX-copy i std::wydajność kopiowania

Starałem się poprawić wydajność operacji kopiowania przez SSE i AVX:

    #include <immintrin.h>

    const int sz = 1024;
    float *mas = (float *)_mm_malloc(sz*sizeof(float), 16);
    float *tar = (float *)_mm_malloc(sz*sizeof(float), 16);
    float a=0;
    std::generate(mas, mas+sz, [&](){return ++a;});

    const int nn = 1000;//Number of iteration in tester loops    
    std::chrono::time_point<std::chrono::system_clock> start1, end1, start2, end2, start3, end3; 

    //std::copy testing
    start1 = std::chrono::system_clock::now();
    for(int i=0; i<nn; ++i)
        std::copy(mas, mas+sz, tar);
    end1 = std::chrono::system_clock::now();
    float elapsed1 = std::chrono::duration_cast<std::chrono::microseconds>(end1-start1).count();

    //SSE-copy testing
    start2 = std::chrono::system_clock::now();
    for(int i=0; i<nn; ++i)
    {
        auto _mas = mas;
        auto _tar = tar;
        for(; _mas!=mas+sz; _mas+=4, _tar+=4)
        {
           __m128 buffer = _mm_load_ps(_mas);
           _mm_store_ps(_tar, buffer);
        }
    }
    end2 = std::chrono::system_clock::now();
    float elapsed2 = std::chrono::duration_cast<std::chrono::microseconds>(end2-start2).count();

    //AVX-copy testing
    start3 = std::chrono::system_clock::now();
    for(int i=0; i<nn; ++i)
    {
        auto _mas = mas;
        auto _tar = tar;
        for(; _mas!=mas+sz; _mas+=8, _tar+=8)
        {
           __m256 buffer = _mm256_load_ps(_mas);
           _mm256_store_ps(_tar, buffer);
        }
    }
    end3 = std::chrono::system_clock::now();
    float elapsed3 = std::chrono::duration_cast<std::chrono::microseconds>(end3-start3).count();

    std::cout<<"serial - "<<elapsed1<<", SSE - "<<elapsed2<<", AVX - "<<elapsed3<<"\nSSE gain: "<<elapsed1/elapsed2<<"\nAVX gain: "<<elapsed1/elapsed3;

    _mm_free(mas);
    _mm_free(tar);
To działa. Jednak podczas gdy liczba iteracji w tester-loops - NN - wzrasta, przyrost wydajności simd-copy maleje:

Nn=10: SSE-gain=3, AVX-gain=6;

Nn=100: SSE-gain=0,75, AVX-gain=1,5;

Nn=1000: SSE-gain=0,55, AVX-gain=1,1;

Czy ktoś może wyjaśnić, co jest przyczyną wspomnianego efektu spadku wydajności i czy jest to wskazane jest ręczne wektoryzowanie operacji kopiowania?

Author: gorill, 2013-08-19

5 answers

Problem polega na tym, że twój test wykonuje słabą pracę, aby przenieść niektóre czynniki w sprzęcie, które utrudniają benchmarking. Aby to przetestować, stworzyłem własny przypadek testowy. Coś takiego:

for blah blah:
    sleep(500ms)
    std::copy
    sse
    axv

Wyjście:

SSE: 1.11753x faster than std::copy
AVX: 1.81342x faster than std::copy

Więc w tym przypadku AVX jest dużo szybszy niż std::copy. Co się stanie, gdy zmienię na test case na..

for blah blah:
    sleep(500ms)
    sse
    axv
    std::copy
Zauważ, że absolutnie nic się nie zmieniło, poza kolejnością testów.
SSE: 0.797673x faster than std::copy
AVX: 0.809399x faster than std::copy

Woah! jak to możliwe? Procesor zajmuje trochę czasu, aby pełna prędkość, więc testy, które są uruchamiane później mają przewagę. To pytanie ma teraz 3 odpowiedzi, w tym odpowiedź "zaakceptowaną". Ale tylko ten z najniższą liczbą głosów był na dobrej drodze.

Jest to jeden z powodów, dla których benchmarking jest trudny i nigdy nie powinieneś ufać niczyim mikro-benchmarkom, chyba że zawierają one szczegółowe informacje o ich konfiguracji. Nie tylko kod może pójść źle. Funkcje oszczędzania energii i dziwne sterowniki mogą całkowicie zepsuć twój benchmark. Kiedyś zmierzyłem różnicę w wydajności czynnika 7, przełączając przełącznik w BIOSie, który oferuje mniej niż 1% notebooków.

 22
Author: Stefan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-19 13:17:32

To bardzo interesujące pytanie, ale uważam, że żadna z odpowiedzi do tej pory nie jest poprawna, ponieważ samo pytanie jest tak mylące.

Tytuł powinien zostać zmieniony na "Jak osiągnąć teoretyczną przepustowość We/Wy pamięci?"

Bez względu na to, jaki zestaw instrukcji jest używany, procesor jest o wiele szybszy od PAMIĘCI RAM, że czysta Kopia pamięci blokowej jest w 100% ograniczona We/Wy. I to wyjaśnia, dlaczego nie ma małej różnicy między wydajnością SSE i AVX.

Dla małych bufory gorące w pamięci podręcznej L1D, AVX może kopiować znacznie szybciej niż SSE na procesorach takich jak Haswell, gdzie ładunki/sklepy 256b naprawdę używają ścieżki danych 256b do pamięci podręcznej L1D zamiast dzielić się na dwie operacje 128b.

Jak na ironię, starożytna Instrukcja X86rep stosq działa znacznie lepiej niż SSE i AVX pod względem kopii pamięci!

Artykuł tutaj wyjaśnia, jak naprawdę dobrze nasycić przepustowość pamięci i ma bogate odniesienia do dalszych badań.

Zobacz także Enhanced REP MOVSB for memcpy tutaj NA SO, gdzie odpowiedź @ BeeOnRope omawia sklepy NT (i sklepy non-RFO wykonane przez rep stosb/stosq) vs.zwykłe sklepy, i jak przepustowość pamięci jednordzeniowej jest często ograniczona przez maksymalną współbieżność / opóźnienie, a nie przez sam kontroler pamięci.

 4
Author: PhD EcE,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-19 21:11:26

Myślę, że to dlatego, że pomiar nie jest dokładny dla krótkich operacji.

Podczas pomiaru wydajności na procesorze Intel

  1. Wyłącz "Turbo Boost" i "SpeedStep". Możesz to zrobić w systemie BIOS.

  2. Zmień priorytet procesu / wątku na wysoki lub w czasie rzeczywistym. Dzięki temu Twój wątek będzie działał.

  3. Ustaw maskę procesowego procesora tylko na jeden rdzeń. Maskowanie PROCESORA z wyższym priorytetem zminimalizuje przełączanie kontekstu.

  4. Użyj _ _ rdtsc() funkcja wewnętrzna. Seria Intel Core zwraca wewnętrzny licznik zegara procesora za pomocą _ _ rdtsc (). Otrzymasz 3400000000 liczy / sekundę z procesora 3,4 Ghz. A _ _ rdtsc () usuwa wszystkie zaplanowane operacje w procesorze, dzięki czemu może dokładniej mierzyć czas.

To mój testowy kod startowy do testowania kodów SSE / AVX.

    int GetMSB(DWORD_PTR dwordPtr)
    {
        if(dwordPtr)
        {
            int result = 1;
    #if defined(_WIN64)
            if(dwordPtr & 0xFFFFFFFF00000000) { result += 32; dwordPtr &= 0xFFFFFFFF00000000; }
            if(dwordPtr & 0xFFFF0000FFFF0000) { result += 16; dwordPtr &= 0xFFFF0000FFFF0000; }
            if(dwordPtr & 0xFF00FF00FF00FF00) { result += 8;  dwordPtr &= 0xFF00FF00FF00FF00; }
            if(dwordPtr & 0xF0F0F0F0F0F0F0F0) { result += 4;  dwordPtr &= 0xF0F0F0F0F0F0F0F0; }
            if(dwordPtr & 0xCCCCCCCCCCCCCCCC) { result += 2;  dwordPtr &= 0xCCCCCCCCCCCCCCCC; }
            if(dwordPtr & 0xAAAAAAAAAAAAAAAA) { result += 1; }
    #else
            if(dwordPtr & 0xFFFF0000) { result += 16; dwordPtr &= 0xFFFF0000; }
            if(dwordPtr & 0xFF00FF00) { result += 8;  dwordPtr &= 0xFF00FF00; }
            if(dwordPtr & 0xF0F0F0F0) { result += 4;  dwordPtr &= 0xF0F0F0F0; }
            if(dwordPtr & 0xCCCCCCCC) { result += 2;  dwordPtr &= 0xCCCCCCCC; }
            if(dwordPtr & 0xAAAAAAAA) { result += 1; }
    #endif
            return result;
        }
        else
        {
            return 0;
        }
    }

    int _tmain(int argc, _TCHAR* argv[])
    {
        // Set Core Affinity
        DWORD_PTR processMask, systemMask;
        GetProcessAffinityMask(GetCurrentProcess(), &processMask, &systemMask);
        SetProcessAffinityMask(GetCurrentProcess(), 1 << (GetMSB(processMask) - 1) );

        // Set Process Priority. you can use REALTIME_PRIORITY_CLASS.
        SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS);

        DWORD64 start, end;
        start = __rdtsc();
    // your code here.
        end = __rdtsc();
        printf("%I64d\n", end - start);
        return 0;
    }
 3
Author: zupet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-20 06:25:34

Pisanie szybkiego SSE nie jest tak proste, jak Użycie operacji SSE zamiast ich nie-równoległych odpowiedników. W tym przypadku podejrzewam, że Twój kompilator nie może rozwinąć pary load/store i twój czas jest zdominowany przez przerwy spowodowane użyciem wyjścia jednej operacji o niskiej przepustowości (load) w następnej instrukcji (store).

Możesz przetestować ten pomysł ręcznie rozwijając jedno nacięcie:

//SSE-copy testing
start2 = std::chrono::system_clock::now();
for(int i=0; i<nn; ++i)
{
    auto _mas = mas;
    auto _tar = tar;
    for(; _mas!=mas+sz; _mas+=8, _tar+=8)
    {
       __m128 buffer1 = _mm_load_ps(_mas);
       __m128 buffer2 = _mm_load_ps(_mas+4);
       _mm_store_ps(_tar, buffer1);
       _mm_store_ps(_tar+4, buffer2);
    }
}

Normalnie podczas używania intrinsics demontuję wyjście i upewniam się, że nic szalonego się nie dzieje (możesz spróbować sprawdzić, czy / jak oryginalna pętla została rozwinięta). Dla bardziej złożonych pętli właściwym narzędziem jest Intel Architecture Code Analyzer (IACA) . Jest to narzędzie do analizy statycznej, które może powiedzieć ci rzeczy takie jak "masz stragany rurociągów".

 3
Author: Ben Jackson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-04 20:22:58

Myślę,że twoim głównym problemem / wąskim gardłem jest Twój _mm_malloc.

Sugeruję użycie std::vector jako głównej struktury danych, jeśli martwisz się o lokalizację w C++.

intrinsics nie są do końca "biblioteką", są bardziej jak wbudowana funkcja dostarczana z twojego kompilatora, powinieneś zapoznać się z wewnętrznymi dokumentami kompilatora przed użyciem tej funkcji.

Należy również zauważyć, że fakt, że {[2] } są nowsze niż SSE nie czyni AVX szybszym, niezależnie od tego, co planujesz użyć, liczba cykli wykonanych przez funkcję jest prawdopodobnie ważniejsza niż argument" avx vs sse", na przykład patrz Ta odpowiedź.

Spróbuj Z POD int array[] lub std::vector.

 1
Author: user2485710,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:02:42