SSE4

2012-07-04 13 views
10

kullanarak Nokta Ürünü Hesaplamasını Vektör Hale Getirmek Bu kodu SSE4 nokta ürünü ile geliştirmeye çalışıyorum ama bir çözüm bulmakta zorlanıyorum. Bu fonksiyon, her biri 80 hücreyle float dizileri içeren qi ve tj parametrelerini alır ve daha sonra nokta ürününü hesaplar. Dönüş değeri dört nokta ürünlü bir vektördür. Yani, yapmaya çalıştığım şey, yirmi değerin dört noktalı ürününü paralel olarak hesaplamak.SSE4

Bu kodu nasıl geliştireceğiniz hakkında bir fikriniz var mı?

inline __m128 ScalarProd20Vec(__m128* qi, __m128* tj) 
{ 
    __m128 res=_mm_add_ps(_mm_mul_ps(tj[0],qi[0]),_mm_mul_ps(tj[1],qi[1])); 
    res=_mm_add_ps(res,_mm_add_ps(_mm_mul_ps(tj[2],qi[2]),_mm_mul_ps(tj[3],qi[3]))); 
    res=_mm_add_ps(res,_mm_add_ps(_mm_mul_ps(tj[4],qi[4]),_mm_mul_ps(tj[5],qi[5]))); 
    res=_mm_add_ps(res,_mm_add_ps(_mm_mul_ps(tj[6],qi[6]),_mm_mul_ps(tj[7],qi[7]))); 
    res=_mm_add_ps(res,_mm_add_ps(_mm_mul_ps(tj[8],qi[8]),_mm_mul_ps(tj[9],qi[9]))); 
    res=_mm_add_ps(res,_mm_add_ps(_mm_mul_ps(tj[10],qi[10]),_mm_mul_ps(tj[11],qi[11]))); 
    res=_mm_add_ps(res,_mm_add_ps(_mm_mul_ps(tj[12],qi[12]),_mm_mul_ps(tj[13],qi[13]))); 
    res=_mm_add_ps(res,_mm_add_ps(_mm_mul_ps(tj[14],qi[14]),_mm_mul_ps(tj[15],qi[15]))); 
    res=_mm_add_ps(res,_mm_add_ps(_mm_mul_ps(tj[16],qi[16]),_mm_mul_ps(tj[17],qi[17]))); 
    res=_mm_add_ps(res,_mm_add_ps(_mm_mul_ps(tj[18],qi[18]),_mm_mul_ps(tj[19],qi[19]))); 
    return res; 
} 

cevap

9

SO üzerinde gördüğüm yüzlerce SSE örneğinden başlayarak, kodunuz başlangıçtan oldukça iyi durumda olanlardan biridir. SSE4 nokta-ürün talimatına ihtiyacınız yok. (Sen daha iyisini yapabilir!)

Ancak deneyebileceğiniz bir şey var: (. Ben henüz zaman aşımına değil çünkü denemek demek)

Şu anda bir veri bağımlılık zinciri var res. Vektör ekleme bugün çoğu makinede 3-4 döngüdür.

__m128 res0 = _mm_add_ps(_mm_mul_ps(tj[ 0],qi[ 0]),_mm_mul_ps(tj[ 1],qi[ 1])); 
__m128 res1 = _mm_add_ps(_mm_mul_ps(tj[ 2],qi[ 2]),_mm_mul_ps(tj[ 3],qi[ 3])); 

res0 = _mm_add_ps(res0,_mm_add_ps(_mm_mul_ps(tj[ 4],qi[ 4]),_mm_mul_ps(tj[ 5],qi[ 5]))); 
res1 = _mm_add_ps(res1,_mm_add_ps(_mm_mul_ps(tj[ 6],qi[ 6]),_mm_mul_ps(tj[ 7],qi[ 7]))); 

res0 = _mm_add_ps(res0,_mm_add_ps(_mm_mul_ps(tj[ 8],qi[ 8]),_mm_mul_ps(tj[ 9],qi[ 9]))); 
res1 = _mm_add_ps(res1,_mm_add_ps(_mm_mul_ps(tj[10],qi[10]),_mm_mul_ps(tj[11],qi[11]))); 

res0 = _mm_add_ps(res0,_mm_add_ps(_mm_mul_ps(tj[12],qi[12]),_mm_mul_ps(tj[13],qi[13]))); 
res1 = _mm_add_ps(res1,_mm_add_ps(_mm_mul_ps(tj[14],qi[14]),_mm_mul_ps(tj[15],qi[15]))); 

res0 = _mm_add_ps(res0,_mm_add_ps(_mm_mul_ps(tj[16],qi[16]),_mm_mul_ps(tj[17],qi[17]))); 
res1 = _mm_add_ps(res1,_mm_add_ps(_mm_mul_ps(tj[18],qi[18]),_mm_mul_ps(tj[19],qi[19]))); 

return _mm_add_ps(res0,res1); 

Bu neredeyse kritik keser: Ne yapabilirsiniz düğüm-bölmek res değişkeni aşağıdaki gibidir

(10 additions on critical path) * (3 cycles addps latency) = 30 cycles 

: Yani kod beri çalıştırmak için 30 döngü en az alacak yarım yol. Kayan noktalı olmayan ilişkililik nedeniyle, derleyicilerin yapması gereken bu optimizasyonun geçersiz olduğunu unutmayın.


İşte 4 yönlü düğüm-bölme ve AMD FMA4 talimatları kullanarak alternatif bir versiyonu. Kaynaştırılmış çoklu ekler ekleyemezseniz, onları bölmek için çekinmeyin. Yukarıdaki ilk versiyondan daha iyi olabilir.

__m128 res0 = _mm_mul_ps(tj[ 0],qi[ 0]); 
__m128 res1 = _mm_mul_ps(tj[ 1],qi[ 1]); 
__m128 res2 = _mm_mul_ps(tj[ 2],qi[ 2]); 
__m128 res3 = _mm_mul_ps(tj[ 3],qi[ 3]); 

res0 = _mm_macc_ps(tj[ 4],qi[ 4],res0); 
res1 = _mm_macc_ps(tj[ 5],qi[ 5],res1); 
res2 = _mm_macc_ps(tj[ 6],qi[ 6],res2); 
res3 = _mm_macc_ps(tj[ 7],qi[ 7],res3); 

res0 = _mm_macc_ps(tj[ 8],qi[ 8],res0); 
res1 = _mm_macc_ps(tj[ 9],qi[ 9],res1); 
res2 = _mm_macc_ps(tj[10],qi[10],res2); 
res3 = _mm_macc_ps(tj[11],qi[11],res3); 

res0 = _mm_macc_ps(tj[12],qi[12],res0); 
res1 = _mm_macc_ps(tj[13],qi[13],res1); 
res2 = _mm_macc_ps(tj[14],qi[14],res2); 
res3 = _mm_macc_ps(tj[15],qi[15],res3); 

res0 = _mm_macc_ps(tj[16],qi[16],res0); 
res1 = _mm_macc_ps(tj[17],qi[17],res1); 
res2 = _mm_macc_ps(tj[18],qi[18],res2); 
res3 = _mm_macc_ps(tj[19],qi[19],res3); 

res0 = _mm_add_ps(res0,res1); 
res2 = _mm_add_ps(res2,res3); 

return _mm_add_ps(res0,res2); 
+3

Bunu düşünmeye gelin. 40 hafıza yükü var. Sandy Bridge işlemcisi kullanmıyorsanız, 40 döngüde darboğazsınız demektir. Bu yüzden OP'nin kodu zaten uygun olabilir. – Mysticial

+2

Kayan nokta ilişkilendirmesi hakkında: Derleyici bayrakların çoğu zaman takdir edilmeyen, yanlış anlaşılan kara koyunları -hızlı-matematik, bazen harikalar yaratır. Ve AMD'ler, insanlığın neredeyse başlangıcından beri, döngü başına iki L1 hafıza yükü yapabilir, ancak maalesef köpekler her yerde yavaşlar. – hirschhornsalz

+0

Yardımlarınız için çok teşekkürler. Test sonucum, kodumun fikriniz kadar hızlı çalıştığını belirtir (yorumda belirttiğiniz gibi). AMD FMA4 ilginç görünüyor ancak bu talimat makinemde mevcut değil ve kod SSE2 uyumlu olmalıdır. -Fast-math ile deneyeceğim. –

3

Öncelikle, yapıyor yapabileceğiniz en önemli optimizasyon emin derleyici tüm optimizasyon ayarları açık vardır. bir döngü olarak yazarsam, onu göz önüne sermek muhtemeldir yüzden


Derleyiciler oldukça akıllıdır: Eğer -funroll-loops geçmesi gerekiyor

__128 res = _mm_setzero(); 
for (int i = 0; i < 10; i++) { 
    res = _mm_add_ps(res, _mm_add_ps(_mm_mul_ps(tj[2*i], qi[2*i]), _mm_mul_ps(tj[2*i+1], qi[2*i+1]))); 
} 
return res; 

(GCC ile, ve daha sonra önüne sermek edeceğiz .) bir seferde 5 yinelemeleri yapmak, örneğin

Ayrıca makro tanımlayabiliriz ve loop versiyonu daha yavaş ise, elle önüne sermek:

__128 res = _mm_setzero(); 

#define STEP(i) res = _mm_add_ps(res, _mm_add_ps(_mm_mul_ps(tj[2*i], qi[2*i]), _mm_mul_ps(tj[2*i+1], qi[2*i+1]))) 

STEP(0); STEP(1); STEP(2); STEP(3); STEP(4); 
STEP(5); STEP(6); STEP(7); STEP(8); STEP(9); 

#undef STEP 

return res; 

Hatta 0 ile 20 arasında döngü çalıştırın (veya makro sürümü ile aynı şeyi) olabilir, yani:

__128 res = _mm_setzero(); 
for (int i = 0; i < 20; i++) { 
    res = _mm_add_ps(res, _mm_mul_ps(tj[i], qi[i])); 
} 
return res; 

(GCC ile ve -funroll-loops bu bir seferde 10 yinelemeleri yapmak açılarak Örneğin, yukarıdaki iki-a-zaman döngüsü ile aynıdır.)

2

Verileriniz, özel SSE4 nokta ürün talimatları için uygun bir biçimde bellekte düzenlenmez (dpps). Bu talimatlar böyle ,, tek bir vektör boyutları bitişik olmasını bekliyoruz:

| v0-dim0 | v1-dim0 | v2-dim0 | v3-dim0 | v0-dim1 | ... 

Geçerli genel yaklaşımı uygun görünüyor: Veri birbirleriyle serpiştirilmiş vektörleri gibi görünüyor oysa

| dim0 | dim1 | dim2 | ... | dim19 | 

- çarpımların sonuçlarının üretildikten hemen sonra kullanılmayacak şekilde talimatlarını yeniden düzenleyerek şeyleri iyileştirebilirsiniz, ancak aslında derleyicinin bunu kendi başına çözebilmesi gerekir.