2015-03-27 19 views
11

Linki wprowadzające znalazłem podczas poszukiwania:Jak wektoryzować moją pętlę za pomocą g ++?

  1. 6.59.14 Loop-Specific Pragmas
  2. 2.100 Pragma Loop_Optimize
  3. How to give hint to gcc about loop count
  4. Tell gcc to specifically unroll a loop
  5. How to Force Vectorization in C++

Jak widać większość z nich jest na C, ale to sądził, że mogą pracować również w C++. Tu jest mój kodu:

template<typename T> 
//__attribute__((optimize("unroll-loops"))) 
//__attribute__ ((pure)) 
void foo(std::vector<T> &p1, size_t start, 
      size_t end, const std::vector<T> &p2) { 
    typename std::vector<T>::const_iterator it2 = p2.begin(); 
    //#pragma simd 
    //#pragma omp parallel for 
    //#pragma GCC ivdep Unroll Vector 
    for (size_t i = start; i < end; ++i, ++it2) { 
    p1[i] = p1[i] - *it2; 
    p1[i] += 1; 
    } 
} 

int main() 
{ 
    size_t n; 
    double x,y; 
    n = 12800000; 
    vector<double> v,u; 
    for(size_t i=0; i<n; ++i) { 
     x = i; 
     y = i - 1; 
     v.push_back(x); 
     u.push_back(y); 
    } 
    using namespace std::chrono; 

    high_resolution_clock::time_point t1 = high_resolution_clock::now(); 
    foo(v,0,n,u); 
    high_resolution_clock::time_point t2 = high_resolution_clock::now(); 

    duration<double> time_span = duration_cast<duration<double>>(t2 - t1); 

    std::cout << "It took me " << time_span.count() << " seconds."; 
    std::cout << std::endl; 
    return 0; 
} 

użyłem AL podpowiedzi Widać komentowane powyżej, ale nie dostałem żadnej przyspieszenie jako pokazy wyjściowych próbki (przy pierwszym uruchomieniu po Odkomentowano to #pragma GCC ivdep Unroll Vector:

[email protected]:~/Downloads$ g++ test.cpp -O3 -std=c++0x -funroll-loops -ftree-vectorize -o test 
[email protected]:~/Downloads$ ./test 
It took me 0.026575 seconds. 
[email protected]:~/Downloads$ g++ test.cpp -O3 -std=c++0x -o test 
[email protected]:~/Downloads$ ./test 
It took me 0.0252697 seconds. 

Czy jest jakaś nadzieja Albo flagi optymalizacji O3 właśnie załatwia sprawę Wszelkie sugestie na przyspieszenie tego kodu (funkcja foo) są mile widziane

Moja wersja g ++:?!

[email protected]:~/Downloads$ g++ --version 
g++ (Ubuntu 4.8.1-2ubuntu1~12.04) 4.8.1 

Należy zauważyć, że ciało pętli jest przypadkowe. Nie jestem interesujący w przepisywaniu go w innej formie.


EDIT

Odpowiedź mówiąc, że nie ma nic więcej, co można zrobić to również dopuszczalne!

+0

Więc spojrzał na zespół, aby sprawdzić, czy jest już wektoryzowane pod '-O3'? – Mysticial

+0

O cholera, nie, nie. Zamierzam to zrobić, sprawdzając to pytanie: http://stackoverflow.com/questions/1289881/using-gcc-to-produce-readable-assembly Dobry pomysł @Mysticial! – gsamaras

+0

@Mysticial może odpowiedź udzielona przez Davida sprawia, że ​​czytanie zgromadzenia nie jest potrzebne? – gsamaras

Odpowiedz

9

Flaga O3 automatycznie włącza się "automatycznie". https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html

-O3 włącza wszystkie optymalizacji określonych -O2 oraz włącza -finline grupy funkcyjne, -funswitch pętle, -fpredictive-commoning, -fgcse-po-przeładowania, -ftree pętla wektoryzacji , -ftree pętli rozprowadzać-wzorów, -ftree-SLP-wektoryzacji, -fvect-kosztową model -ftree-częściowy Pre i opcje -fipa-CP-clone

Tak więc w obu przypadkach kompilator próbuje wykonać wektoryzację pętli.

Korzystanie z g ++ 4.8.2 skompilować z:

g++ test.cpp -O2 -std=c++0x -funroll-loops -ftree-vectorize -ftree-vectorizer-verbose=1 -o test 

Daje to:

Analyzing loop at test.cpp:16                                                            


Vectorizing loop at test.cpp:16                                                            

test.cpp:16: note: create runtime check for data references *it2$_M_current_106 and *_39                                              
test.cpp:16: note: created 1 versioning for alias checks.                                                     

test.cpp:16: note: LOOP VECTORIZED.                                                           
Analyzing loop at test_old.cpp:29                                                            

test.cpp:22: note: vectorized 1 loops in function.                                                       

test.cpp:18: note: Unroll loop 7 times                                                          

test.cpp:16: note: Unroll loop 7 times                                                          

test.cpp:28: note: Unroll loop 1 times 

Kompilacja bez -ftree-vectorize flag tylko

g++ test.cpp -O2 -std=c++0x -funroll-loops -ftree-vectorizer-verbose=1 -o test 

Zwraca to:

test_old.cpp:16: note: Unroll loop 7 times 

test_old.cpp:28: note: Unroll loop 1 times 

Linia 16 jest początek funkcji pętli n, więc kompilator zdecydowanie je wektoryzuje. Sprawdzenie to potwierdza także asembler.

Wydaje się, że otrzymuję agresywne buforowanie na komputerze, z którego aktualnie korzystam, co bardzo utrudnia dokładny pomiar czasu działania funkcji.

Ale oto kilka innych rzeczy można spróbować też:

  • Użyj __restrict__ kwalifikatorem aby poinformować kompilator, że nie pokrywają między macierzami.

  • poinformować kompilator tablice są wyrównane z __builtin_assume_aligned (nie Portable)

Oto mój kod wynikowy (usunąłem szablonu, ponieważ będą chcesz użyć innego dopasowanie do różnych typów danych)

#include <iostream> 
#include <chrono> 
#include <vector> 

void foo(double * __restrict__ p1, 
      double * __restrict__ p2, 
      size_t start, 
      size_t end) 
{ 
    double* pA1 = static_cast<double*>(__builtin_assume_aligned(p1, 16)); 
    double* pA2 = static_cast<double*>(__builtin_assume_aligned(p2, 16)); 

    for (size_t i = start; i < end; ++i) 
    { 
     pA1[i] = pA1[i] - pA2[i]; 
     pA1[i] += 1; 
    } 
} 

int main() 
{ 
    size_t n; 
    double x, y; 
    n = 12800000; 
    std::vector<double> v,u; 

    for(size_t i=0; i<n; ++i) { 
     x = i; 
     y = i - 1; 
     v.push_back(x); 
     u.push_back(y); 
    } 

    using namespace std::chrono; 

    high_resolution_clock::time_point t1 = high_resolution_clock::now(); 
    foo(&v[0], &u[0], 0, n); 
    high_resolution_clock::time_point t2 = high_resolution_clock::now(); 

    duration<double> time_span = duration_cast<duration<double>>(t2 - t1); 

    std::cout << "It took me " << time_span.count() << " seconds."; 
    std::cout << std::endl; 

    return 0; 
} 

Tak jak powiedziałem, miałem problemy z uzyskaniem spójnych pomiarów czasu, więc nie mogę potwierdzić, czy zwiększy to wydajność (a może nawet zmniejszy!)

+0

Bez różnicy! Może "-unroll-loops" jest już włączone przez O2, ale nie mogłem tego potwierdzić. Jeśli masz jakieś inne sugestie, użyj przycisku edycji (zalecane: D). – gsamaras

+0

Tak, po prostu spróbowałem i nie mam różnicy, pozwól, że wypróbuję kilka rzeczy i zobaczę, co mogę znaleźć :) –

+0

Jeśli nie masz nic nowego, mogę zaakceptować odpowiedź, ale musisz dać mi znać! – gsamaras

1

GCC ma rozszerzenia do kompilatora, który tworzy nowe prymitywy, które będą używać instrukcji SIMD. Zajrzyj here, aby uzyskać szczegółowe informacje.

Większość kompilatorów twierdzi, że będzie automatycznie wektoryzować operacje, ale zależy to od dopasowania wzorca kompilatora, ale jak można sobie wyobrazić, może to być bardzo trafione.

+0

Interesujące, ale nie jestem pewien, jaki rozmiar powinienem podać w atrybucie, czy możesz mnie poprowadzić? – gsamaras

+0

Myślę, że wiele architektur ma 128-bitowe rejestry SIMD, więc zachowaj wszystkie obiekty o szerokości 128 bitów. Pamiętaj też, że SIMD nie przyspiesza ładowania danych i czasów przechowywania, tylko przyspiesza operacje arytmetyczne. – doron

+0

Nadal nie jest jasne, jak powinienem to zrobić. Czy jestem ograniczony do 4 wymiarów? Przykład pomógłby. – gsamaras