Добре ли е да се създаде голям масив от AVX/SSE стойности

Паралелизирам определен проблем с динамично програмиране, използвайки инструкции AVX2/SSE.

В основната итерация на моето изчисление изчислявам колона в матрица, където всяка клетка е структура от AVX2 регистри (_m256i). Използвам стойности от предишната колона на матрицата като входни стойности за изчисляване на текущата колона. Колоните могат да бъдат големи, така че това, което правя, е да имам масив от структури (в стека), където всяка структура има два _m256i елемента.

Структура:

struct Cell {
  _m256i first;
  _m256i second;
};

След това имам масив като този: Cell prevColumn [N]. N обикновено ще бъде няколко стотици.

Знам, че _m256i основно представлява avx2 регистър, така че се чудя как да мисля за този масив, как се държи, след като N е много по-голямо от 16 (което е броят на avx регистрите)? Добра практика ли е да се създаде такъв масив или има някакъв по-добър подход, който трябва да използвам, когато съхранявам много _m256i стойности, които скоро ще бъдат повторно използвани?

Освен това има ли някакво подравняване, което трябва да направя с тези структури? Четох много за подравняването, но все още не съм сигурен как и кога точно да го направя.


person Martinsos    schedule 10.05.2015    source източник
comment
кратък отговор: да, можете да създавате масиви, но компилаторът вероятно няма да оптимизира масива в регистри.   -  person Cory Nelson    schedule 10.05.2015
comment
Колко голямо е N? Имайте предвид ограниченията за размера на стека.   -  person Paul R    schedule 10.05.2015
comment
@PaulR N ще бъде около няколко стотици!   -  person Martinsos    schedule 11.05.2015
comment
@CoryNelson може би имате някаква идея как компилаторът ще се справи тогава? Това означава ли, че ще има много зареждане/съхранение? Има ли по-добра практика за това тогава?   -  person Martinsos    schedule 11.05.2015
comment
Добре - тогава доста малък - това не би трябвало да е проблем на настолна или сървърна операционна система.   -  person Paul R    schedule 11.05.2015
comment
Не се притеснявайте за зареждането/съхраняването - така или иначе имате само 16 регистъра - оставете компилатора и L1 кеша да се погрижат за всичко засега.   -  person Paul R    schedule 11.05.2015
comment
Вместо да мислите, че имате масив от AVX/SSE стойности, мислете за това като за SoA или AosOA, което е подходящо за SIMD.   -  person Z boson    schedule 11.05.2015
comment
@Zboson бихте ли разяснили повече за това? Това, което всъщност имам, е масив от структури, където структурата има два елемента, и двата _m256i. Виждам в отговора ви, че AoS не е добър за SIMD, но не разбирам защо?   -  person Martinsos    schedule 11.05.2015
comment
Това за 32-битови или 64-битови цели числа ли е?   -  person Z boson    schedule 11.05.2015
comment
@Zboson Използвам същия алгоритъм за 8-битови, 16-битови и 32-битови цели числа (разбира се, не смесени), прецизността се избира по време на изпълнение и аз пасвам голяма част от тях, както мога в регистъра. Така че правя компромис за прецизно паралелизиране. Но ако трябва да избера такъв, за който да оптимизирам, бих избрал 8-битов. Редактирах отговора си с информация за структурата.   -  person Martinsos    schedule 11.05.2015
comment
@Martinsos, Cell е SoA, когато го съхранявате или четете (напр. typedef union __m256i { int8_t m256_i8[32]; int16_t m256_i16[16]; int32_t m256_i32[8]; } __256i;). Тогава в Cell prevColumn [N] масивът prevColumn е AoSoA.   -  person Z boson    schedule 11.05.2015
comment
@Zboson Добре, благодаря! Така че __m256i всъщност е масив и това прави Cell SoA. Донякъде съм объркан: мислех, че __m256i е компилиран в регистър (това напълно грешно ли е)?   -  person Martinsos    schedule 11.05.2015
comment
Той е или в регистъра, или се съхранява в паметта. Това е същото като всеки от примитивните типове данни, например int или се съхранява в регистър (напр. rdx) или се съхранява в паметта. Компилаторът се грижи за това. Вашият въпрос е аналогичен на въпроса дали е добре да се направи масив от ints (също има 16 скаларни регистъра точно като 16 YMM регистъра). В паметта можете да мислите за __256i като typedef union __m256i { int8_t m256_i8[32]; int16_t m256_i16[16]; int32_t m256_i32[8]; } __256i;, ако желаете.   -  person Z boson    schedule 12.05.2015
comment
Нека продължим тази дискусия в чата.   -  person Martinsos    schedule 12.05.2015


Отговори (1)


По-добре е да структурирате кода си така, че да прави всичко възможно със стойност, преди да продължите. Малките буфери, които се побират в L1 кеша, няма да са много лоши за производителността, но не го правете, освен ако не е необходимо.

Мисля, че е по-типично да напишете кода си с буфери от тип int [], вместо тип __m256i, но не съм сигурен. Така или иначе работи и трябва да накара компилацията да генерира ефективен код. Но начинът int [] означава, че по-малко код трябва да е различен за версията SSE, AVX2 и AVX512. И може да е по-лесно да изследвате нещата с дебъгер, да имате вашите данни в масив с тип, който ще форматира данните добре.

Доколкото разбирам, вътрешните елементи за зареждане/съхраняване са отчасти там като отливка между _m256i и int [], тъй като AVX не се проваля при неподравнени, а просто се забавя при границите на кеша. Присвояването на / от масив от _m256i трябва да работи добре и да генерира инструкции за зареждане/съхранение, където е необходимо, в противен случай генерира векторни инструкции с операнди източник на памет. (за по-компактен код и по-малко операции на слят домейн.)

person Peter Cordes    schedule 08.06.2015
comment
Питър, когато казваш, че AVX няма грешка при неподравнени, имаш предвид, че подравнена инструкция за зареждане (vmovdqa) може също да обработва неподравнени адреси? Ръководството за разработчици на софтуер на Intel казва: Когато операндът източник или дестинация е операнд от паметта, операндът трябва да бъде подравнен на 32-байтова граница или ще бъде генерирано изключение за обща защита (#GP). За да преместите целочислени данни към и от неподравнени местоположения в паметта, използвайте инструкцията VMOVDQU. Въпреки това наблюдавах този ефект в моя код (подравнена инструкция за зареждане, обработваща добре неподравнени адреси), но не посмях да го използвам. Някаква справка? - person Ralf; 26.06.2015
comment
Намерих препратка към моя въпрос в документ на Intel за асемблерния език (от Kreitzer и Domeika): И накрая, за повечето инструкции Intel® AVX премахва ограничението за подравняване на векторните зареждания и съхранявания. Изричните инструкции за „подравняване на преместване“ като VMOVDQA все още изискват адресите да бъдат подравнени по границите на размера на вектора. Но други векторни зареждания и съхранявания могат да бъдат неподравнени. - person Ralf; 26.06.2015
comment
... което води до следния въпрос: Когато C компилаторът генерира AVX инструкции от intrinsics, той вероятно може да вземе изрично подравнено присъщо зареждане като _mm256_load_si256() и да го превърне в машинна инструкция (като добавка) директно с помощта на адреса. Това би довело до изненадващо поведение, че изведнъж данните могат да бъдат заредени от неподравнени адреси. Има ли някакъв начин да се контролира това поведение (влияние, когато компилаторът генерира vmovdqa и когато генерира адресни аргументи в следващите инструкции)? - person Ralf; 26.06.2015
comment
@Ralf: да, сгъването на mm_load вътрешни елементи в операнди на паметта за други insns води до изненади, както правилно предположихте. напр. stackoverflow .com/questions/30329235/. Решението е винаги да използвате loadu intrinsic, така че ако компилаторът иска да използва самостоятелно зареждане, той ще използва movdqu / movups. (Което има идентична производителност с movdqa, когато адресът Е подравнен, на процесори, достатъчно нови, за да поддържат AVX.) - person Peter Cordes; 27.06.2015