Korzystanie z instrukcji SSE


Mam pętlę napisaną w C ++, która jest wykonywana dla każdego elementu dużej tablicy liczb całkowitych. Wewnątrz pętli maskuję niektóre bity liczby całkowitej, a następnie znajduję wartości minimalne i maksymalne. Słyszałem, że jeśli użyję instrukcji SSE do tych operacji, to będzie działać znacznie szybciej w porównaniu do normalnej pętli napisanej przy użyciu AND i warunków bitowych if-else. Moje pytanie brzmi, czy powinienem skorzystać z tych instrukcji SSE? Co się stanie, jeśli mój kod zostanie uruchomiony na innym procesorze? Czy nadal będzie działać, czy te instrukcje są specyficzne dla procesora?
Zaproszony:
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

  • Instrukcje SSE są specyficzne dla procesora. Możesz zobaczyć, który procesor obsługuje daną wersję SSE w Wikipedii.
  • To, czy kod SSE jest szybszy, czy nie, zależy od wielu czynników: Po pierwsze, oczywiście, czy problem dotyczy pamięci, czy procesora. Jeśli szyna pamięci jest wąskim gardłem, SSE niewiele pomoże. Spróbuj uprościć obliczenia liczb całkowitych, jeśli przyspieszy to kod, prawdopodobnie będzie on związany z procesorem i masz duże szanse na przyspieszenie.
  • Należy pamiętać, że pisanie kodu SIMD jest znacznie trudniejsze niż pisanie kodu w C ++, a wynikowy kod jest znacznie trudniejszy do modyfikacji. Zawsze aktualizuj swój kod C ++, będziesz go potrzebować jako komentarz i do walidacji kodu asemblera.
  • Rozważ użycie biblioteki, takiej jak IPP, która implementuje typowe operacje SIMD niskiego poziomu zoptymalizowane dla różnych procesorów.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

SIMD, którego przykładem jest SSE, umożliwia wykonanie tej samej operacji na wielu blokach danych. W związku z tym nie odniesiesz żadnych korzyści z używania SSE jako bezpośredniego zamiennika operacji na liczbach całkowitych, otrzymasz je tylko wtedy, gdy możesz wykonywać operacje na wielu elementach w tym samym czasie. Obejmuje to załadowanie niektórych wartości danych, które są ciągłe w pamięci, wykonanie niezbędnego przetwarzania, a następnie przejście do następnego zestawu wartości w tablicy.
Problemy:
1 Jeśli ścieżka kodu zależy od przetwarzanych danych, implementacja SIMD staje się znacznie trudniejsza. Na przykład:
a = array [index];
a &= mask;
a >>= shift;
if (a < somevalue)
{
a += 2;
array [index] = a;
}
++index;

nie jest to tak łatwe do zrobienia jak 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 Jeśli dane nie są ciągłe, ładowanie danych do instrukcji SIMD jest kłopotliwe
Kod 3 jest specyficzny dla procesora. SSE występuje tylko w IA32 (Intel/AMD) i nie wszystkie procesory IA32 obsługują SSE.
Musisz przeanalizować algorytm i dane, aby sprawdzić, czy może to być SSEd, a to wymaga wiedzy, jak działa SSE. Witryna firmy Intel jest pełna dokumentacji.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Ten problem jest doskonałym przykładem tego, gdzie potrzebny jest dobry program profilujący niskiego poziomu. (Coś w rodzaju VTune) To może dać ci znacznie bardziej świadomy obraz tego, gdzie są twoje hotspoty.
Myślę, że z tego, co opisujesz, twój router prawdopodobnie będzie miał błędy przewidywania gałęzi spowodowane obliczeniami min/max przy użyciu if/else. więc użycie wbudowanych funkcji SIMD pozwoli ci na użycie instrukcji min/max, jednak warto spróbować zamiast tego użyć zdalnego obliczania min/max. W ten sposób można osiągnąć większość wyników przy mniejszym bólu.
Coś w tym stylu:
inline int 
minimum(int a, int b)
{
int mask = (a - b) >> 31;
return ((a & mask) | (b & ~mask));
}
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Jeśli używasz instrukcji SSE, jesteś oczywiście ograniczony do procesorów, które je obsługują.
Oznacza to, że x86 wraca do Pentium 2 (nie pamiętam dokładnie, kiedy zostały wprowadzone, ale to było dawno temu)
SSE2, o ile pamiętam, jest tym, który oferuje operacje na liczbach całkowitych, nieco nowszy (Pentium 3? Chociaż wczesne procesory AMD Athlon ich nie obsługiwały)
W każdym razie masz dwie możliwości skorzystania z tych instrukcji. Alternatywnie, napisz cały blok kodu w asemblerze (to prawdopodobnie zły pomysł. Uniemożliwia to kompilatorowi optymalizację kodu, a napisanie wydajnego asemblera jest bardzo trudne).
Użyj również funkcji wewnętrznych dostępnych w kompilatorze (jeśli pamięć się nie zmienia, są one zwykle zdefiniowane w xmmintrin.h)
Ale z drugiej strony wydajność może się nie poprawić. Kod SSE ma dodatkowe wymagania dotyczące przetwarzanych danych. Przede wszystkim należy pamiętać, że dane muszą być wyrównane w granicach 128-bitowych. Między wartościami ładowanymi do tego samego rejestru powinny być również niewielkie lub żadne zależności (128-bitowy rejestr SSE może zawierać 4 int. Dodanie do siebie pierwszej i drugiej nie jest optymalne. Ale dodanie wszystkich czterech int do odpowiedniego 4 int w inny rejestr będzie szybki)
Może być kuszące, aby użyć biblioteki, która opakowuje wszystkie skrzypce SSE niskiego poziomu, ale może to również zrujnować potencjalną przewagę wydajności.
Nie wiem, jak dobra jest obsługa operacji na liczbach całkowitych SSE, więc może to być również czynnik, który może ograniczać wydajność. SSE ma głównie na celu przyspieszenie operacji zmiennoprzecinkowych.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Jeśli zamierzasz używać Microsoft Visual C ++, przeczytaj to:
http://www.codeproject.com/KB/ recipes/sseintro. aspx
http://www.codeproject.com/KB/ ... .aspx
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Zaimplementowaliśmy kod przetwarzania obrazu podobny do tego, który opisujesz, ale w tablicy bajtów, w SSE. Przyspieszenie w porównaniu z kodem C jest znaczące, w zależności od dokładnego algorytmu, ponad 4 razy, nawet w stosunku do kompilatora Intela. Jednak, jak wspomniałeś, masz następujące wady:
  • Ruchliwość. Kod będzie działał na każdym procesorze typu Intel i AMD, ale nie na innych procesorach. Nie stanowi to dla nas problemu, ponieważ kontrolujemy docelowy sprzęt. Przełączanie kompilatorów, a nawet 64-bitowego systemu operacyjnego może również stanowić problem.
  • Masz stromą krzywą uczenia się, ale odkryłem, że kiedy już zrozumiesz zasady, pisanie nowych algorytmów nie jest takie trudne.
  • Konserwowalność. Większość programistów C lub C ++ nie zna asemblera/SSE.

Moja rada dotyczy tego tylko wtedy, gdy naprawdę potrzebujesz zwiększenia wydajności i nie możesz znaleźć funkcji dla swojego problemu w bibliotece takiej jak Intel IPP i jeśli możesz żyć z problemami z przenośnością.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Napisz kod, który pomoże kompilatorowi zrozumieć, co robisz. GCC zrozumie i zoptymalizuje kod SSE w następujący sposób:
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;
};
};

Pamiętaj tylko, aby mieć -msse -msse2 w opcjach kompilacji!
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Instrukcje SSE były pierwotnie tylko na układach Intela, ale ostatnio (od czasu Athlona?) AMD również je obsługuje, więc jeśli robisz kod w oparciu o zestaw instrukcji SSE, powinieneś być przenośny dla większości procesów x86.
To powiedziawszy, możesz nie chcieć spędzać czasu na nauce kodowania SSE, jeśli nie znasz jeszcze asemblera x86 - opcją x-simpler może być sprawdzenie dokumentacji kompilatora i sprawdzenie, czy są opcje umożliwiające kompilatorowi automatyczne generowanie kodu SSE dla ty. Niektóre kompilatory bardzo dobrze wektoryzują pętle w ten sposób. (Prawdopodobnie nie będziesz zaskoczony, gdy usłyszysz, że kompilatory Intela są w tym dobre :)
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Z doświadczenia mogę powiedzieć, że SSE zapewnia ogromne (4x lub więcej) przyspieszenie w porównaniu ze zwykłą wersją kodu c (bez wbudowanego asm, bez wbudowanych funkcji), ale ręcznie zoptymalizowany asembler może przewyższyć zestaw generowany przez kompilator, jeśli kompilator tego nie robi Nie mogę dowiedzieć się, do czego zmierza programista (zaufaj mi, kompilatory nie obejmują wszystkich możliwych kombinacji kodu i nigdy tego nie zrobią).
A kompilator nie może za każdym razem rozłożyć danych, które działa tak szybko, jak to możliwe.
Ale potrzebujesz dużego doświadczenia, aby przyspieszyć kompilator Intel (jeśli to możliwe).
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Wbudowane funkcje SIMD (takie jak SSE2) mogą przyspieszyć ten proces, ale prawidłowe korzystanie z nich wymaga doświadczenia. Są bardzo wrażliwe na wyrównanie linii i opóźnienia; nieostrożne użytkowanie może pogorszyć wydajność, niż byłoby bez nich. Uzyskasz znacznie prostsze i natychmiastowe przyspieszenie, po prostu używając wstępnego pobierania pamięci podręcznej, aby upewnić się, że wszystkie twoje int znajdują się w L1 na czas, abyś mógł z nimi pracować.
O ile Twoja funkcja nie wymaga przepustowości większej niż 100 000 000 liczb całkowitych na sekundę, SIMD prawdopodobnie nie jest wart zachodu.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Aby szybko dodać do tego, co zostało powiedziane wcześniej, różne wersje SSE są dostępne na różnych procesorach: można to zweryfikować, patrząc na odpowiednie flagi funkcji zwrócone przez instrukcję CPUID (więcej szczegółów w dokumentacji firmy Intel).
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Patrzeć na

asembler inline

dla C/C ++, tutaj

artykuł
http://www.ddj.com/cpp/184401967
DDJ. Jeśli nie jesteś pewien, czy Twój program będzie działał na kompatybilnej platformie, powinieneś postępować zgodnie z wytycznymi, które wielu tutaj podało.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Chociaż prawdą jest, że SSE jest specyficzne dla niektórych procesorów (SSE może być stosunkowo bezpieczne, z mojego doświadczenia SSE2 jest znacznie mniejsze), możesz wykryć procesor w czasie wykonywania i ładować kod dynamicznie w zależności od docelowego procesora.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Zgadzam się z poprzednimi plakatami. Korzyść może być dość duża, ale jej uzyskanie może wymagać dużo pracy. Dokumentacja firmy Intel dotycząca tych instrukcji ma ponad 4000 stron. Możesz bezpłatnie sprawdzić EasySSE (biblioteka c wrappers ++ wbudowanych funkcji + przykłady) od Ocali Inc.
Zakładam, że moje powiązanie z tym EasySSE jest jasne.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Nie polecam robienia tego samodzielnie, chyba że dobrze znasz się na montażu. Korzystanie z SSE prawdopodobnie będzie wymagało dokładnej reorganizacji danych, jak wskazuje Skizz, a użyteczność tego jest często wątpliwa.
Prawdopodobnie znacznie lepiej byłoby, gdybyś pisał bardzo małe pętle i utrzymywał dane bardzo ściśle zorganizowane i polegał tylko na kompilatorze, który zrobi to za Ciebie. Zarówno kompilator Intel C, jak i GCC (od wersji 4.1) mogą automatycznie wektoryzować kod i prawdopodobnie zrobią to lepiej niż Ty. (Po prostu dodaj -ftree-vectorize do swoich CXXFLAGS.)

Edit
: jeszcze jedna rzecz, o której powinienem wspomnieć, to fakt, że niektóre kompilatory obsługują

osadzony

funkcje assemblera, które prawdopodobnie byłyby łatwiejsze w użyciu niż składnia asm () lub __asm ​​{}.

Aby odpowiedzieć na pytania, Zaloguj się lub Zarejestruj się