Šta je novo?

Problem sa SSE

genuine

Slavan
Učlanjen(a)
17.02.2006
Poruke
1,906
Poena
350
Ukoliko neko ima ideju sta bi trebalo izmeniti pa da se povecaju performanse ovog koda... na internetu nisam nasao nista posto nije obicno mnozenje matrica...
Pozdrav svima
p.s.
u pitanju je transformacija vektora matricom(mnozenje) pri cemu se potom vektor prebacuje iz homogenih koordinata u "normalne"...

const Matrix& Matrix::multiplay(Vector3D& operand)const
{
#ifndef _SSE_OPTIMIZATIONS_
Vector3D result(operand);
operand.x = result.x * this->matrixData[0][0] + result.y * this->matrixData[1][0] + result.z * this->matrixData[2][0] + this->matrixData[3][0] ;
operand.y = result.x * this->matrixData[0][1] + result.y * this->matrixData[1][1] + result.z * this->matrixData[2][1] + this->matrixData[3][1] ;
operand.z = result.x * this->matrixData[0][2] + result.y * this->matrixData[1][2] + result.z * this->matrixData[2][2] + this->matrixData[3][2] ;

float w = result.x * this->matrixData[0][3] + result.y * this->matrixData[1][3] + result.z * this->matrixData[2][3] + this->matrixData[3][3] ;

operand *= 1/w;

#else

_asm{
mov eax , operand
mov ecx , [this]
add ecx , Matrix::matrixData

movss xmm0,[eax];
movss xmm1,[eax+4]
movss xmm2,[eax+8]
movss xmm3, _SES_ONE_

shufps xmm0,xmm0,_MM_SHUFFLE(0, 0, 0, 0)
shufps xmm1,xmm1,_MM_SHUFFLE(0, 0, 0, 0)
shufps xmm2,xmm2,_MM_SHUFFLE(0, 0, 0, 0)
movss xmm5,xmm3
shufps xmm3,xmm3,_MM_SHUFFLE(0, 0, 0, 0)

mulps xmm0,[ecx]
mulps xmm1,[ecx+16]
mulps xmm2,[ecx+32]
mulps xmm3,[ecx+48]
addps xmm0,xmm1
addps xmm2,xmm3
addps xmm0,xmm2


movaps xmm1,xmm0
divss xmm5,xmm5
shufps xmm5,xmm5,_MM_SHUFFLE(0, 0, 0, 0)
shufps xmm1,xmm0,_MM_SHUFFLE(3, 3, 3, 3)

mulps xmm0,xmm5
movaps [eax],xmm0

}

#endif
return *this;
}
 
Radi li taj tvoj kod uopste?

movss xmm3, _SES_ONE_ // cak i da je _SSE_ONE_ ne moze konstanta nego mem/reg operand

Cemu ti sluzi deljenje broja (pretpostavljam 1.0f) samim sobom (divss xmm5, xmm5)?

Koristis movaps, da li ti je memorija 16-byte aligned?
 
Ok vidim gresku, radio sam neke dve verzije gde je jedna trebala da koristi rcpss instrukciju ali je ipak greska prevelika pa sam onda prepravljao za divps a ovo je nesto izmedju ( naravno ne radi) ovo je donji deo :

movaps xmm1,xmm0 sacuvam vektor posle mnozenja
shufps xmm1,xmm0,_MM_SHUFFLE(3, 3, 3, 3) uzmem samo w komponentu
divss xmm5,xmm1 uradim 1.0f/w
shufps xmm5,xmm5,_MM_SHUFFLE(0, 0, 0, 0) pa sad uzmem samo 1.0f/w
mulps xmm0,xmm5 pomnozim sa vektorom
movaps [eax],xmm0 i vratim rezultat

_SES_ONE je static float _SES_ONE[] { 1.0f}; posto nisam znao kako da ucitam konstantu drugacije a ostalo je sve poravnato sa 16 bajtova za movaps
medjutim ubrzanje nije bas nesto preveliko u odnosu na ono sto izbaci vc++ 7.0 kompajler za FPU.

pa me zanima pod pretpostavkom da ovo radi ( sada bi trebalo ) zasto performanse nisu bolje ? ( ubrzanje reda 10%)
jel zbog kompajlera ( moji losi test primeri koje kompajler optimizuje )
ili zbog toga sto imam Athlon 1600+
ili bi kod trebalo da se preuredi zbog medjuzavisnosti izmedju podataka...
na netu sam gledao neke primere ali koliko sam skontao slicne su stvari?
p.s. nov sam u ovome i svaka bi mi pomoc dobrodosla...
hvala unapred
 
ulovio sam jos nekoliko gluposti.. na primer mnozio sam kolonu matrice sa 1.0f,1.0f,1.0f,1.0f sto je gutracenje 1 shufps i 1 mulps i 1 addss instrukcije... dobro napreduje polako🙂)
 
movaps xmm1,xmm0 sacuvam vektor posle mnozenja
shufps xmm1,xmm0,_MM_SHUFFLE(3, 3, 3, 3) uzmem samo w komponentu

Ovo mi je cudno takodje. Stavljas xmm0 u xmm1, a zatim radis shufps sa xmm1 kao destination. Cemu onda movaps?

Nisu mi jasni Vector3D (result i operand) u tvom kodu. Jednom pristupas skalarno (eax, eax+4, eax+8), a drugom vektorski (ecx, ecx+16, ecx+32, ecx+48 -- po cetiri elementa odjednom). Sta ti tu zapravo radis?

Vidi, pristup ti je malcice zbrda-zdola da ne kazem pogresan. Trebas prvo da napravis kod koji radi ispravno (moras imati test okruzenje da mozes da uporedis rezultate sa C++ originalom) pa onda da ubrzavas ako ima sta da se ubrza. Ovako ces izgubiti puno vremena, a rezultati ce biti losi.

Evo ti jos par saveta:

Sto se tice operand = operand * 1 / w dela, w odnosno 1 / w moras izracunati i pripremiti dosta ranije u kodu ovako kako je sada divss koji ima veliku latenciju se ne izvrsi ceo, a iza njega sledi lanac zavisnih instrukcija odnosno sve shufps i mulps instrukcije cekaju da se deljenje zavrsi.

Sto se tice:

float konstanta = 1.0f;

movss xmm3, dword ptr [konstanta]
shufps xmm3,xmm3,_MM_SHUFFLE(0, 0, 0, 0)

Uglavnom je brze napisati:

float konstanta[4] = { 1.0f, 1.0f, 1.0f, 1.0f };

pa onda:

movaps xmm3, xmmword ptr [konstanta]

Naravno i ovde mora biti poravnato (stack) na 16 bajtova da bi movaps radio.

No sve to nema veze jer verovatno je problem u tome sto ti zoves tu funkciju vise puta za po jedan vektor sto je pogresno. Treba da napravis petlju koja ce raditi ovu operaciju nad nizom vektora da bi zaista postigao vece ubrzanje. Takodje, cini mi se da je problem i u tome sto pristupas matrici po spoljnoj dimenziji smanjujuci pritom lokalitet podataka. Sreca pa je matrica mala (dve linije cachea kod Athlon XP, jedna kod P4) ali moguce da i to ima malcice udela. Uvek je bolje pristupati sukcesivnim elementima (onima koji su susedni u istom redu dakle [0][0], [0][1], [0][2], ...).

Najbolje bi zapravo bilo da ti to sto hoces da uradis napises prvo kao cist C kod sa strukturama pa da ga okacis kao .zip ovde da pogledam kad stignem ako hoces. Mozda na taj nacin nesto i postignemo.
 
E hvala puno na savetima... odlucio sam da odbacim za pocetak deljenje sa w i da ostanem u homogenim koordinatama a naravno
samo pitanjce oko onog ucitavanja jedinice?
cini mi se da sam probao da ucitam ka float[4] {1,1,1,1} ali je bilo sporije nego
movss i shuffle bar kod mene... pogledacu opet to
da ne duzim evo kod :
 

Prilozi

Poslednja izmena:
Najbolje sto se moze za ovako kratko vreme, u principu isto sto si i sam uradio osim sto sam koristio druge registre. EAX i EDX se ne cuvaju preko poziva f-je pa je najbolje koristiti njih.

Kad se pozove member funkcija neke klase this pointer se prenosi kao "skriveni" parametar u ECX.

Kod:
	__asm	{
		mov	eax, [operand]
		lea	edx, [ecx + Matrix::matrixData]
		movss	xmm0, dword ptr [eax]
		movss	xmm1, dword ptr [eax + 4]
		movss	xmm2, dword ptr [eax + 8]
		movss	xmm3, dword ptr [eax + 12]

		shufps	xmm0, xmm0, 0x00
		shufps	xmm1, xmm1, 0x00
		shufps	xmm2, xmm2, 0x00
		shufps	xmm3, xmm3, 0x00

		mulps	xmm0, xmmword ptr [edx]
		mulps	xmm1, xmmword ptr [edx + 16]
		mulps	xmm2, xmmword ptr [edx + 32]
		mulps	xmm3, xmmword ptr [edx + 48]

		addps	xmm0, xmm1
		addps	xmm0, xmm2
		addps	xmm0, xmm3

		movaps	xmmword ptr [eax], xmm0
	}

U optimizacijama u VC iskljuci frame pointer, i dodaj arch:SSE i bice jos malo brze sveukupno.

clock() ima rezoluciju od 1 / CLOCKS_PER_SEC pa nije bas najpreciznija za merenje. Najbolje je koristiti RDTSC ili QueryPerformanceCounter().

Inace, kad se prevede bez SSE optimizacija (zakomentarisan makro), dobije se 5x sporije vreme kod mene tako da mislim da nisi u pravu kad kazes da je samo 10%. Ali mozda je to samo moj spori P4 koji slabo radi sa legacy FPU kodom.
 
Ekstra, hvala puno ...

sto se brzine tice mislim da moj stari amd tu pravi problema ali svaki procenat mi znaci.... probacu da ispravim sve sto treba pa cu da vidim za rezultate...
 
Зашто немаш нигде prefetchnta инструкцију?
Велика је вероватноћа да ћеш одједном трансформисати више вектора, а не само један, тако да на почетку обраде тренутног вектора можеш довући у кеш наредни.
 
sa prefetchnta bi trebalo da dovlacim sledeci vektor od onog koji trenutno obradjujem ili prekosledeci.. e da kad sam vec kod toga jel ima neka empirijska formula koja govori koji blok treba da dovucem sa prefetchnta u odnosu na tekuci posto performanse variraju od takta procesora i organizacije , da zanemarim memoriju i magistralu ( u algoritmu mnozenja matrice vektorom )?
koliko sam ja skontao ako je razdaljina previse mala izmedju vektora koji trenutno obradjujem i onog kod ocu u kesu onda on nece stici da se dovuce u kes na vreme pa nema tolikog efekta.. npr . kod mene na athlon 1700+ kada kazem prefetchnta[eax + 64] dobijem za 10% gore rezultate od prefetchnta[eax +128] isto ako kazem prefetchnta [eax+512] opet je 10 posto gore pa ocigledno ima nekakav minimum...(kako da ga nadjem za proizvoljan amdov odnosno intelov procesor?)




takodje bi trebalo da koristim i movntps da pri upisu zaobidjem kes ukoliko mi trenutno rezultat nije potreban...


p.s.
Kako mi rece covek , microsoftov kompajler drzi this pokazivac u ecx registru..
Sta je sa intelovim kompajlerom posto sam pokusavao da prebacim stvar na linux? koliko sam video u debageru this nije u ecx. jel ima neki nacin da se kaze kompajleru da ocu this u ecx (intelovom)
 
Poslednja izmena:
За довлачење наредне кеш линије потребно је ( емпиријских ) 50-100 циклуса.

Athlon64 има кеш линију од 64 бајта. То је 16 float-oва, тојест 4 вектора од по 4 float-а.

Зато је најбоље да направиш функцију која 4 вектора множи једном матрицом, а да на њеном ПОЧЕТКУ иницираш довлачење следећа 4 вектора.
Притом претпостављам да су ти сви вектори сукцесивно поређани у меморији, у противном _mm_prefetch нема смисла.

Наравно ово прво ћеш урадити тако што једну исту функцију за множење вектора матрицом copy&paste-ујеш још 3 пута па само измениш адресе. НИКАКВЕ for петље не долазе у обзир, то ће ти уништити брзину!
 
Poslednja izmena:
Узгред, зашто не користиш intrinsic функције уместо asm-а?
У првом случају дајеш компајлеру шансе да оптимизује SSE код (на пример да елиминише коришћење меморијских локација уколико је могуће да све променљиве стану у xmm регистре, да премести инструкције тако да се pipeline што боље искористи итд). Из искуства могу да кажем да је јако тешко "ручно" написати SSE код који је бољи од вешто написаног intrinsic кода.
 
Slazem se.. pokusao sam i nije uspelo... i ja sam se nadao da ce kompajler da optimizuje bolje ali ili ja negde gresim ( 99% verovatno ) ili je nesto drugo.. posto

npr kada hocu da kazem :
mulps xmm1 , [eax]
u asembleru ..

"c" ekvivalent bi bio
__m128 a,b;
a = _mm_load_ps(blabla1);
b = _mm_load_ps(blabla2);
b = _mm_mul_ps(a,b)

u asm kodu sam izostavio ucitavanje u xmm1 i eax ali je sustina ta da sam se nadao da kompajler nece ucitavati i blabla1 i blabla2 u xmm registre vec da ce uraditi ono sto je gore navedeno... ali nije i performanse se razlikuju bas solidno... ovo vazi samo za primere gde zelim da izvrsavam operacije sa operandima direktno iz memorije npr za pojedinacno mnozenje matrice i vektora.. naravno da paketna obrada daje mnogo bolje performanse ali paketna obrada mi konkretno ne pomaze .. ( ne transformisem trimesh vec recimo racunam nekakva odbijanja,prelamanja i slicne gluposti)...
slazem se da je bolje i mozda preglednije drugo za paketne stvari ali mislim da za pojedinacne bolje radi inline asm..
i jos jedno pitanjce..
video sam da je neko deklarisao vektor kao uniju float[4] i __m128 .. sta u stvari to znaci?
p.s.
 
3MaJ je napisao(la):
Зашто немаш нигде prefetchnta инструкцију?
Велика је вероватноћа да ћеш одједном трансформисати више вектора, а не само један, тако да на почетку обраде тренутног вектора можеш довући у кеш наредни.

PREFETCHNTA je NON-TEMPORAL¹ prefetch HINT².

¹ - Podatak se ne dovlaci u cache nego u interne buffere (kojih ima mali broj) da se ne bi "prljala" cache hijerarhija.

² - Hint znaci "sugestija". Procesor moze da poslusa ili da ne poslusa, a implementacija se razlikuje od revizije do revizije procesora.

Previse prefetch-a daje kontra efekat, stvarajuci veci broj transakcija na busu nego sto je pozeljno. Najbolje je organizovati podatke tako da se prefetch podrazumeva (P4 ima hardverski prefetcher).

@genuine:
Ja sam ti lepo rekao da treba da reorganizujes podatke da bi maksimizovao protok. Znaci, suludo je pozivati funkciju za jedan vektor pa zatim za drugi, itd, pogotovo ako ti vektori nisu jedan iza drugog u memoriji ili ako se ne daj boze dinamicki kreiraju tik pred racunanje -- u tom slucaju nikakav prefetch ti nece pomoci.
Takodje, ako ti jedan vektor ima 16 bajtova (4x float) moras obraditi dva vektora u jednom prolazu petlje ako ti je cilj P3/Athlon XP, ili cak cetiri vektora ako radis za P4/A64. Drugim recima petlja bi trebala biti razmotana dva ili cetiri puta u zavisnosti od duzine cache linije procesora na kome ce se izvrsavati.

genuine je napisao(la):
e da kad sam vec kod toga jel ima neka empirijska formula koja govori koji blok treba da dovucem sa prefetchnta u odnosu na tekuci

Postoji nesto sto se zove Mathematics of Prefetch Scheduling Distance u Intelovom Optimization Manual-u (appendix E). Nemoj samo da pomislis da je jednostavno, nekad je lakse empirijski ustanoviti to probom. Da bi probanje imalo smisla moras imati ultra precizno merenje vremena. clock() slobodno zaboravi jer mu je rezolucija premala.

genuine je napisao(la):
Sta je sa intelovim kompajlerom posto sam pokusavao da prebacim stvar na linux? koliko sam video u debageru this nije u ecx. jel ima neki nacin da se kaze kompajleru da ocu this u ecx (intelovom)

__thiscall is used by default in Microsoft compilers for member functions in 16 and 32 bit mode

Intelov kompajler za linux postuje GCC ABI kako bi kod generisan njime mogao da se linkuje sa GCC generisanim kodom. Ne znam detalje za g++ odnosno ne znam kako on prosledjuje this pointer, moraces sam to da potrazis.

3MaJ je napisao(la):
Зато је најбоље да направиш функцију која 4 вектора множи једном матрицом, а да на њеном ПОЧЕТКУ иницираш довлачење следећа 4 вектора.

To nije dobro zato sto ce prefetch biti uzaludan ako je to poslednji poziv funkcije. Zato to mnozenje ne treba da bude u funkciji nego u petlji ciji broj iteracija mora biti deljiv sa 2 (za 32-bajtne cache linije) ili sa 4 (64-bajtne cache linije). Ukoliko broj iteracija nije deljiv sa 2 ili 4, petlja mora imati "rep" koji ce da odradi poslednje 1-3 pojedinacne iteracije. Nekada je jednostavnije uraditi jednu iteraciju vise nego pisati repove. Znaci ako imas 201 vektor a radis sa 4 vektora u jednom prolazu petlje onda alociras prostor za 204 vektora i odradis 51 iteraciju kroz petlju zanemarujuci naknadno 3 poslednja vektora koji su tu samo radi padovanja.

Inace mozes pisati intrinsic i ovako:

__m128 a;
a = _mm_mul_ps(_mm_load_ps(blabla1), _mm_set_ps(4.0, 3.0, 2.0, 1.0))

To ce dovesti do kakve-takve optimizacije.

Ja sam ipak pre za asembler, a iz licnog iskustva preporucujem asemblerski kod u odvojenim asm fajlovima uz korsicenje NASM asemblera jer ce tako kod biti moguce prevesti i pod windowsom i pod linuxom (nasm ima normalnu Intel sintaksu nasuprot umobolne %$()":, simbolima prenatrpane AT&T sintakse).

Razlog vise za ovo je sto kompajleri koji generisu kod za 64-bitni windows ne podrzavaju vise inline asm.

genuine je napisao(la):
video sam da je neko deklarisao vektor kao uniju float[4] i __m128 .. sta u stvari to znaci?

Znaci da mozes pristupati individualnim elementima vektora preko float i skalarnog SSE koda ili celom vektoru preko intrinsica.

MOVNTPS je NON-TEMPORAL store koji takodje zaobilazi cache i koristi buffere za direktan upis u memoriju. Ukoliko se podaci nece skoro koristiti moze da se stavi umesto MOVAPS. Medjutim, pre daljeg rada sa tim podacima a iza petlje koja radi upis ovom instrukcijom mora se staviti SFENCE instrukcija radi ocuvanja koherencije podataka. Iz mog licnog iskustva MOVNTPS pomaze u jako malom broju veoma specificnih algoritama. Bolje ne trosi vreme na to.
 
Poslednja izmena:
Nazad
Vrh Dno