2013-02-19 20 views
15

SSE kullanarak bir vektör (u) ile 4x4 matris (M) çarpmanın en verimli uygulanmasını bulmaya çalışıyorum. Ben Mu = v anlamınaSSE ile verimli 4x4 matris vektör çarpımı: yatay ekleme ve noktalama ürünü - ne anlamı var?

Bildiğim kadarıyla bu konuda gitmek için iki temel yolu vardır anladığım kadarıyla. 2 SSE2 içinde uygulanması kolaydır

method 1) v1 = dot(row1, u), v2 = dot(row2, u), v3 = dot(row3, u), v4 = dot(row4, u) 
    method 2) v = u1 col1 + u2 col2 + u3 col3 + u4 col4. 

Yöntemi. Yöntem 1, SSE3'te yatay ekleme talimatı veya SSE4'teki nokta ürün talimatıyla uygulanabilir. Bununla birlikte, tüm testlerim 2'de, her zaman yöntem 1'den daha iyi performans gösterir.

Yöntem 1'in avantajına sahip olmanın bir avantajı, örneğin affin dönüşümü için 3x4 matrisindedir. Bu durumda son nokta ürünü gereksizdir. Ancak bu durumda bile bir 4x4 matrisindeki yöntem 2, 3x4 matrisinde yöntem 1'den daha hızlıdır. 4x4 matrisinde yöntem 2'den daha hızlı bulduğum tek yöntem, 4x3 matrisinde yöntem 2'dir.

Yatay ekleme ve nokta ürün talimatının amacı nedir? Aslında, nokta üretim talimatı bu durumda en kötü performansı verir. Belki de veri formatıyla ilgili bir şey var mı? Matrisin nasıl sıralandığını tanımlayamazsa, o zaman bir transpoze ihtiyaç vardır ve bu durumda belki yöntem 1 daha iyi olur? Bazı kodlar için aşağıya bakın.

__m128 m4x4v_colSSE(const __m128 cols[4], const __m128 v) { 
    __m128 u1 = _mm_shuffle_ps(v,v, _MM_SHUFFLE(0,0,0,0)); 
    __m128 u2 = _mm_shuffle_ps(v,v, _MM_SHUFFLE(1,1,1,1)); 
    __m128 u3 = _mm_shuffle_ps(v,v, _MM_SHUFFLE(2,2,2,2)); 
    __m128 u4 = _mm_shuffle_ps(v,v, _MM_SHUFFLE(3,3,3,3)); 

    __m128 prod1 = _mm_mul_ps(u1, cols[0]); 
    __m128 prod2 = _mm_mul_ps(u2, cols[1]); 
    __m128 prod3 = _mm_mul_ps(u3, cols[2]); 
    __m128 prod4 = _mm_mul_ps(u4, cols[3]); 

    return _mm_add_ps(_mm_add_ps(prod1, prod2), _mm_add_ps(prod3, prod4)); 
} 

__m128 m4x4v_rowSSE3(const __m128 rows[4], const __m128 v) { 
    __m128 prod1 = _mm_mul_ps(rows[0], v); 
    __m128 prod2 = _mm_mul_ps(rows[1], v); 
    __m128 prod3 = _mm_mul_ps(rows[2], v); 
    __m128 prod4 = _mm_mul_ps(rows[3], v); 

    return _mm_hadd_ps(_mm_hadd_ps(prod1, prod2), _mm_hadd_ps(prod3, prod4)); 
} 

__m128 m4x4v_rowSSE4(const __m128 rows[4], const __m128 v) { 
    __m128 prod1 = _mm_dp_ps (rows[0], v, 0xFF); 
    __m128 prod2 = _mm_dp_ps (rows[1], v, 0xFF); 
    __m128 prod3 = _mm_dp_ps (rows[2], v, 0xFF); 
    __m128 prod4 = _mm_dp_ps (rows[3], v, 0xFF); 

    return _mm_shuffle_ps(_mm_movelh_ps(prod1, prod2), _mm_movelh_ps(prod3, prod4), _MM_SHUFFLE(2, 0, 2, 0)); 
} 

cevap

10

Yatay eklenti ve nokta ürün talimatları karmaşıktır: bunlar sadece basit talimatlar gibi işlemci tarafından yürütülen birden daha basit MİKROİŞLEMLERİN olarak ikiye ayrılmıştır. Yatay ekleme ve noktalama ürün yönergelerinin mikro işlemlere tam olarak ayrıştırılması işlemciye özgüdür, ancak son Intel işlemciler için yatay ekleme 2 SHUFFLE + 1 ADD mikro işlemine ayrıştırılır ve nokta çarpımı 1 MUL + 1 SHUFFLE + 2 ADD mikro işlemlerine ayrıştırılır. Daha fazla sayıda mikroişlemenin yanı sıra, bu talimatlar işlemci boru hattındaki yönerge kod çözücüsünü de vurgulamaktadır: Intel işlemciler, döngü başına yalnızca bir karmaşık komutun kodunu çözebilir (4 basit talimatla karşılaştırılmıştır). AMD Buldozer'de bu karmaşık talimatların göreceli maliyeti daha yüksektir.

+0

Teşekkürler, bu talimatların neden yavaş olduğunu açıklıyor. Ancak, neden uygulandığını açıklamıyor. Ama sanırım şimdi biliyorum. Yöntem 2, verilerin optimal olması için dizinin bir dizisi (SoA), yani sipariş edilen bir sütun olmasını gerektirir. Eğer veri bir dizi yapı (AoS) ise, yani sipariş edilen sıra, bir aktarım yapılmalı ve bu durumda yöntem 1 çok daha hızlıdır. Başka bir deyişle, veriler tanımlanabilirse, bir AoS yerine bir SoA yapın ve yöntem 2'yi kullanın. Aksi takdirde, yatay ekleme ile yöntem 1'i kullanın. Matris çarpımı için nokta üretim talimatını kullanmayın. –

+1

CPU satıcıları, çok yararlı olabilecek yeni yönergeler ekleme geçmişine sahiptir, ancak başlangıçta çok az donanımı bunları uygulamak için ayırmaktadır. Yeterli programlar tarafından benimsenirse, daha sonra talimatı daha hızlı yapmak için sonunda daha fazla donanım eklerler. Birinci jenerasyon '' _mm_dp_ps'', bunu yapmak için her zamanki SSE ya da SSE3 yaklaşımından daha hızlı değildir, ancak teoride, bir çok şeyi yapıyorsanız, biraz daha az kod-bloat olmalıdır. –

+0

Intel Intrinsics kılavuzuna bakarsanız: [link] (https://software.intel.com/sites/landingpage/IntrinsicsGuide/#techs=SSE3,SSE4_1&cats=Arithmetic&expand=2737,2084), performans rakamlarını görürsünüz. Bu ayrıca dp-solüsyonunun neden hadd-solüsyonu tarafından bile çok daha iyi performans gösterdiğini açıklamaya yardımcı olmalıdır. – St0fF