SIMD Intrinsics не са толкова страшни, но трябва ли да ги използваме?

Програмирането на ниско ниво е грях или добродетел? Зависи.

Когато програмирам за използване на векторна обработка на съвременен процесор, в идеалния случай бих написал някакъв код на любимия си език и той ще работи възможно най-бързо „автоматично магически“.

Освен ако току-що не сте започнали да програмирате миналата седмица, подозирам, че знаете, че не така работи светът. Най-доброто представяне идва само с усилия. Оттук и въпросът ми: колко ниско трябва да отидем?

Определени векторни операции

Операцията „вектор“ е математическа операция, която прави повече от една операция. Векторното добавяне може да добави осем двойки числа вместо обикновеното добавяне, което добавя само една двойка числа. Помислете дали да помолите компютъра да събере две числа заедно. Можем да го направим с редовна инструкция за добавяне. Помислете за това да помолите компютъра да добави осем чифта числа един към друг (изчислете C1 = A1 + B1, C2 = A2 + B2, ... C8 = A8 + B8). Можем да направим това с инструкция за добавяне на вектор .

Указанията за вектор включват събиране, изваждане, умножение и други операции.

 SIMD: паралелизъм за вектори

Компютърните учени имат изискано име за векторни инструкции: SIMD или „Single Instruction Multiple Data“. Ако мислим за обикновена инструкция за добавяне като SISD (Single Instruction Single Data), където single означава единична двойка входове за данни, тогава векторното добавяне е SIMD, където множеството може да означава осем двойки данни.

Обичам да наричам SIMD „другият хардуерен паралелизъм“, тъй като за „паралелизъм“ в компютрите толкова често се мисли, че идва от наличието на множество ядра. Броят на основните ядра постоянно се увеличава. Четири броя ядра са често срещани, 20 или повече са често срещани в процесорите за сървъри, а най-големият брой ядра на Intel днес е 72 ядра в един процесор Intel® Xeon Phi ™.

Размерите на векторни инструкции също се увеличават. Ранните векторни инструкции, като SSE, изпълняват едновременно до четири операции. Най-голямата векторна ширина на Intel днес, в AVX-512, изпълнява до 16 операции наведнъж.

 Колко ниско трябва да отидем?

С толкова много резултати, колко работа трябва да свършим, за да използваме това изпълнение?

 Отговорът е много и ето защо: Четири ядра могат да ни ускорят най-много 4 пъти. AVX (половината от размера на AVX-512, но много по-често срещан) може да ни ускори до 8 пъти най-много. Комбинирани, те могат да получат до 32X. Правенето и на двете има много смисъл.

Ето моя прост списък с това как да се опитам да използвам векторни инструкции (в реда, в който трябва да се опитаме да ги приложим):

 1.     Първо се обадете на библиотека, която върши работата (най-добрата имплицитна векторизация). Пример за такава библиотека е Intel® Math Kernel Library (Intel® MKL). Цялата работа по използването на векторни инструкции е извършена от някой друг. Ограниченията са очевидни: Трябва да намерим библиотека, която да прави това, от което се нуждаем.

2.     Второ, използвайте имплицитна векторизация. Останете абстрактни и го напишете сами, като използвате шаблони или компилатори, за да помогнете. Много компилатори имат ключове за векторизация и опции. Компилаторите вероятно ще бъдат най-преносимият и стабилен начин. Има много шаблони за векторизация, но никой не е видял достатъчно използване с течение на времето, за да бъде ясен победител (скорошно влизане е Intel® SIMD шаблони за оформление на данни [Intel® SDLT]).

3.     Трето, използвайте явна векторизация. Това стана много популярно през последните години и се опитва да реши проблема да остане абстрактно, но принуждава компилатора да използва векторни инструкции, когато иначе не би ги използвал. Поддръжката за SIMD в OpenMP е ключовият пример тук, където заявките за векторизация за компилатора са дадени много изрично. Нестандартни разширения съществуват в много компилатори, често под формата на опции или „прагми“. Ако поемете по този маршрут, OpenMP е пътят, ако сте в C, C ++ или Fortran.

4.     И накрая, станете ниски и мръсни. Използвайте присъщите SIMD. Това е като асемблерен език, но написано във вашата програма C / C ++. Вътрешността на SIMD всъщност прилича на извикване на функция, но обикновено създава единична инструкция (векторна инструкция за работа, известна също като SIMD инструкция).

Вътрешността на SIMD не е зло; те обаче са крайна мярка. Първите три избора винаги са по-поддържаеми за в бъдеще, когато работят. Когато обаче първите три не успеят да отговорят на нашите нужди, определено трябва да опитаме да използваме SIMD присъщи елементи.

 Ако искате да започнете да използвате присъщите SIMD, ще имате сериозен крак, ако сте свикнали с програмирането на асемблерен език. Най-вече това е така, защото ще имате по-лесно време за четене на документацията, която обяснява операциите, включително отличното онлайн „Ръководство за вътрешните работи“ на Intel. Ако сте напълно нови в това, попаднах на скорошен блог („SSE: имайте предвид пропастта!“), Който има нежна ръка при въвеждането на присъщи неща. Също така харесвам „Смачкване на числа с AVX и AVX2.“

 Ако библиотека или компилатор могат да направят това, от което се нуждаете, присъщите SIMD не са най-добрият избор. Те обаче имат своето място и не са трудни за използване, след като свикнете с тях. Опитайте ги. Ползите от производителността могат да бъдат невероятни. Виждал съм присъщи SIMD, използвани от умни програмисти за код, който няма компилатор да създаде.

Дори ако опитаме присъщи SIMD и в крайна сметка оставим библиотека или компилатор да свършат работата, наученото може да бъде безценно за разбирането на най-доброто използване на библиотека или компилатор за векторизация. И това може да е най-добрата причина да опитаме SIMD присъщи при следващия път, когато имаме нужда от нещо, за да използваме векторни инструкции.

Щракнете тук, за да изтеглите безплатната си 30-дневна пробна версия на Intel Parallel Studio XE