2014-09-19 10 views
17

Robię trochę przetwarzania obrazu, z którego czerpię korzyści z wektoryzacji. Mam funkcję, która wektoryzuje ok, ale dla których nie jestem w stanie przekonać kompilator, że bufor wejściowy i wyjściowy nie nakładają się, więc nie jest konieczne sprawdzanie aliasów. Powinienem móc to zrobić, używając __restrict__, ale jeśli bufory nie są zdefiniowane jako __restrict__ po pojawieniu się jako argument funkcji, nie ma sposobu, aby przekonać kompilator, że jestem absolutnie pewny, że 2 bufory nigdy się nie pokrywają.Automatyczne wektoryzacja: Przekonanie kompilatora, że ​​sprawdzanie aliasu nie jest konieczne

to funkcja:

__attribute__((optimize("tree-vectorize","tree-vectorizer-verbose=6"))) 
void threshold(const cv::Mat& inputRoi, cv::Mat& outputRoi, const unsigned char th) { 

    const int height = inputRoi.rows; 
    const int width = inputRoi.cols; 

    for (int j = 0; j < height; j++) { 
     const uint8_t* __restrict in = (const uint8_t* __restrict) inputRoi.ptr(j); 
     uint8_t* __restrict out = (uint8_t* __restrict) outputRoi.ptr(j); 
     for (int i = 0; i < width; i++) { 
      out[i] = (in[i] < valueTh) ? 255 : 0; 
     } 
    } 
} 

Jedyny sposób można przekonać kompilator aby nie wykonywać sprawdzenie alias czy umieścić wewnętrzną pętlę odrębną funkcję, w której wskaźniki są określane jako __restrict__ argumenty. Jeśli zadeklaruję tę wewnętrzną funkcję jako wstawioną, ponownie włącza się sprawdzanie aliasu.

można zobaczyć efekt także z tego przykładu, który moim zdaniem jest zgodna: http://goo.gl/7HK5p7

(Uwaga: wiem, że mogłyby być lepsze sposoby pisania tę samą funkcję, ale w tym przypadku jestem po prostu staramy się dowiedz się, jak uniknąć aliasu)

Edytuj:
Problem został rozwiązany !! (Patrz answer below)
Korzystanie z gcc 4.9.2, here is the complete example. Zauważ użycie flagi kompilatora -fopt-info-vec-optimized zamiast zastąpionej -ftree-vectorizer-verbose=N.
Tak więc, dla gcc, użyj #pragma GCC ivdep i ciesz się! :)

+1

Pamiętaj, że problem z wpisaniem może zostać naprawiony dla gcc-5: https://gcc.gnu.org/ml/gcc-patches/2014-09/msg00606.html –

+0

Dziękujemy za wyświetlenie kompilatora WWW w wersji C++ – StarShine

+0

nie mam już gotowej kopii openCV do przetestowania, ale może uda ci się przekonać kompilator, że 'inputRoi' i' outputRoi' odwołują się do różnych buforów za pomocą instrukcji '__assume (in! = out)'? Jest dużo rzeczy, które można zrobić z '__assume', ale to zależy w dużej mierze od tego, czy kompilator jest wystarczająco inteligentny, aby to zrozumieć. – Stefan

Odpowiedz

4

jeśli używasz Intel kompilator, można spróbować włączyć linię:

#pragma ivdep 

dodaje się ustęp cytat z Intel instrukcji kompilator:

ivdep Pragma instruuje kompilator ignorujący założone zależności . Aby zapewnić poprawny kod, kompilator traktuje założoną zależność jako udowodnioną zależność, która zapobiega wektoryzacji. Ta pragma zastępuje tę decyzję. Skorzystaj z tej pragmy tylko wtedy, gdy znasz , że założone zależności pętli są bezpieczne do zignorowania.

W gcc, jeden należy dodać linię:

#pragma GCC ivdep 

wewnątrz funkcji i tuż przed pętlą chcesz wektoryzacji (patrz documentation). Jest to obsługiwane tylko począwszy od gcc 4.9, a przy okazji, korzystanie z __restrict__ jest zbędne.

+0

Wydaje się to bardzo dobrą wskazówką! Nie mogę szybko przetestować na http://gcc.godbolt.org, ponieważ brakuje kompilatora gcc 4.9 i rozumiem, że tej funkcji nie było w poprzednich wersjach kompilatora ... – Antonio

+0

Proszę dać mi znać, czy działa, czy nie. Przez około dwie trzecie czasu ta sztuczka działa dla prostych pętli. Jeśli nadal masz trudności z automatycznym wektoryzowaniem kodu, przypisanie wskaźników wejściowych do fałszywych wskaźników wewnątrz funkcji może być w stanie oszukać kompilator. –

+1

I proszę wprowadź #pragma wewnątrz zagnieżdżonej pętli. #pragma ivdep nie działa poza pętlą zagnieżdżoną przez większość czasu (kompilator intel) –

1

Kompilator Intel co najmniej od wersji 14 nie generuje testów aliasingowych dla threshold2 w kodzie powiązanym, co wskazuje, że Twoje podejście powinno działać. Jednak auto-wektoryzacja gcc nie wykorzystuje tej możliwości do optymalizacji, ale generuje wektoryzowany kod, testy poprawnego wyrównania, testy aliasingu i niezaworyzowanego kodu fall-back/clean-up.

+0

Pytanie jest wystarczająco ostre, rzeczy takie jak to, co może mieć wpływ lub dostosowanie pamięci, nie ma znaczenia. Uważam, że istotną częścią twojej odpowiedzi jest wynik kompilatora Intela, który może żyć w prostym komentarzu. – Antonio

+0

@Antonio: Dlaczego to jest istotne dla Ciebie, czy gcc generuje alias, czy nie? – user1225999

+0

Ponieważ dla mojego przypadku użycia (przetwarzania obrazu), to sprawdzenie nastąpi kilka razy (w każdym wierszu obrazu, dla obrazu lub subimage, które mogą mieć małą liczbę kolumn) – Antonio

2

Innym podejściem do tego konkretnego problemu, który jest wystandaryzowany i w pełni przenośny w (względnie nowoczesnym) kompilatorze, jest użycie OpenMP simd directive, który jest częścią standardu od wersji 4.0. Następnie kod staje:

void threshold(const unsigned char* inputRoi, const unsigned char valueTh, 
       unsigned char* outputRoi, const int width, 
       const int stride, const int height) { 
    #pragma omp simd 
    for (int i = 0; i < width; i++) { 
     outputRoi[i] = (inputRoi[i] < valueTh) ? 255 : 0; 
    } 
} 

A gdy skompilowany ze wsparciem OpenMP włączona (albo z pełną obsługą lub tylko częściowe jednym dla simd tylko, jak z -qopenmp-simd dla kompilatora Intel), a następnie kod jest w pełni wektoryzowane.

Dodatkowo daje to możliwość wskazania możliwego wyrównania wektorów, co może się przydać w pewnych okolicznościach. Na przykład, że twoje tablice wejściowe i wyjściowe zostały przyznane z wyrównującą-aware pamięci podzielnika, takie posix_memalign() z wymogu wyrównanie 256b, wówczas kod może stać:

void threshold(const unsigned char* inputRoi, const unsigned char valueTh, 
       unsigned char* outputRoi, const int width, 
       const int stride, const int height) { 
    #pragma omp simd aligned(inputRoi, outputRoi : 32) 
    for (int i = 0; i < width; i++) { 
     outputRoi[i] = (inputRoi[i] < valueTh) ? 255 : 0; 
    } 
} 

ten powinien następnie pozwalają generować jeszcze szybszy plik binarny. Ta funkcja nie jest łatwo dostępna za pomocą dyrektyw ivdep. Tym bardziej powodów, dla których warto zastosować dyrektywę OpenMP simd.

+0

Dzięki, dowiedziałeś się czegoś nowego z tego posta! –