Soru Intel AVX: çift duyarlıklı kayan nokta değişkenleri için nokta ürününün 256 bit sürümü


Intel Gelişmiş Vektör Uzantıları (AVX), hiçbir teklif sunmuyor çift ​​duyarlıklı kayan noktalı değişkenler için 256 bit sürümünde (YMM kaydı) nokta ürünü. "Neden?" soru başka bir forumda çok kısaca ele alındı ​​(İşte) ve Yığın Taşması Üzerine (İşte). Ancak karşılaştığım soru bu eksik talimatı diğer AVX talimatları ile verimli bir şekilde nasıl değiştirebiliriz?

Tek duyarlıklı kayan noktalı değişkenler için 256 bit sürümündeki nokta ürünü vardır.burada referans):

 __m256 _mm256_dp_ps(__m256 m1, __m256 m2, const int mask);

Fikir, bu eksik talimat için etkili bir eşdeğer bulmaktır:

 __m256d _mm256_dp_pd(__m256d m1, __m256d m2, const int mask);

Daha spesifik olmak gerekirse, değiştirmek istediğim kod __m128 (dört yüzer) __m256d (4 çift) aşağıdaki talimatları kullanın:

   __m128 val0 = ...; // Four float values
   __m128 val1 = ...; //
   __m128 val2 = ...; //
   __m128 val3 = ...; //
   __m128 val4 = ...; //

   __m128 res = _mm_or_ps( _mm_dp_ps(val1,  val0,   0xF1),
                _mm_or_ps( _mm_dp_ps(val2,  val0,   0xF2),
                _mm_or_ps( _mm_dp_ps(val3,  val0,   0xF4),
                           _mm_dp_ps(val4,  val0,   0xF8) )));

Bu kodun sonucu bir _m128 arasındaki nokta ürünlerinin sonuçlarını içeren dört yüzer vektör val1 ve val0, val2 ve val0, val3 ve val0, val4 ve val0.

Belki bu öneriler için ipuçları verebilir?


21
2018-05-04 18:21


Menşei


Fikir için teşekkürler, ancak uygulamada çifte hassasiyete devam etmeliyim. - gleeen.gould
Ayrıca, dönüşüm + float nokta ürünü, çift nokta ürününden daha fazla zaman alacaktır. - Gunther Piez


Cevaplar:


4 * çift çarpma kullanırdım, o zaman hadd (ki bu maalesef, üst ve alt yarıda sadece 2 * 2 yüzer) ekliyor, üst yarıyı ayıklamak (bir karıştırma aynı hızda olmalı, belki daha hızlı) ve alt yarısına ekleyelim.

Sonuç düşük 64 bit dotproduct.

__m256d xy = _mm256_mul_pd( x, y );
__m256d temp = _mm256_hadd_pd( xy, xy );
__m128d hi128 = _mm256_extractf128_pd( temp, 1 );
__m128d dotproduct = _mm_add_pd( (__m128d)temp, hi128 );

Düzenle:
Norbert P.'nin bir fikrinden sonra, bu sürümü bir defada 4 nokta ürünü yapmak için genişlettim.

__m256d xy0 = _mm256_mul_pd( x[0], y[0] );
__m256d xy1 = _mm256_mul_pd( x[1], y[1] );
__m256d xy2 = _mm256_mul_pd( x[2], y[2] );
__m256d xy3 = _mm256_mul_pd( x[3], y[3] );

// low to high: xy00+xy01 xy10+xy11 xy02+xy03 xy12+xy13
__m256d temp01 = _mm256_hadd_pd( xy0, xy1 );   

// low to high: xy20+xy21 xy30+xy31 xy22+xy23 xy32+xy33
__m256d temp23 = _mm256_hadd_pd( xy2, xy3 );

// low to high: xy02+xy03 xy12+xy13 xy20+xy21 xy30+xy31
__m256d swapped = _mm256_permute2f128_pd( temp01, temp23, 0x21 );

// low to high: xy00+xy01 xy10+xy11 xy22+xy23 xy32+xy33
__m256d blended = _mm256_blend_pd(temp01, temp23, 0b1100);

__m256d dotproduct = _mm256_add_pd( swapped, blended );

21
2018-05-04 18:42



Öneri için teşekkürler, bu iyi çalışıyor. Daha spesifik olmak için sorumu düzenledim. - gleeen.gould
Downvoter, açıklamaya dikkat et? - Gunther Piez
@drhirsch: harika bir fikir. Ama gleeen.gould haklı, ekstra bir karıştırmaya ihtiyacın var. Ben tavsiye ediyorum: __m256d swapped = _mm256_permute2f128_pd( temp01, temp23, 0x21 ); __m256d mixed = _mm256_blend_pd(temp01, temp23, 12); __m256d dotproduct = _mm256_add_pd( swapped, mixed );. Bunun tek nedeni VPERM2F128 1 döngü vs 2 döngü alır VBLENDPD. (Umarım sabitleri doğru anladım) - Norbert P.
@ gleeen.gould: AVX2 zaten çıktı mı? olduğunu düşünmüştüm 2013 yılında Haswell'e geliyor. Sandy Bridge'deki şu anki AVX kuşağı hakkında konuşuyordum: bkz. Agner Fog'ın talimat tabloları, s. 129. - Norbert P.
Not: ile bile AVX2 ve vpermpd kullanarak çözüm vextractf128 ve addpd sonuçtaki uygulamadan daha düşük bir birleşik gecikme vpermpd ve vhaddpd yatay toplamı oluşturmak için. - Pixelchemist


Uzardım drhirsch'ın cevabı iki nokta ürününü aynı anda gerçekleştirmek için, bazı işlerden tasarruf etmek:

__m256d xy = _mm256_mul_pd( x, y );
__m256d zw = _mm256_mul_pd( z, w );
__m256d temp = _mm256_hadd_pd( xy, zw );
__m128d hi128 = _mm256_extractf128_pd( temp, 1 );
__m128d dotproduct = _mm_add_pd( (__m128d)temp, hi128 );

Sonra dot(x,y) düşük çift ve dot(z,w) yüksek çifte dotproduct.


12
2018-05-05 05:06





Tek bir nokta ürünü için, sadece dikey bir çarpma ve yatay toplamıdır (bkz. X86 yatay float vektör toplamı yapmak için en hızlı yolu). hadd 2 karı + bir maliyeti add. Her iki girdi ile de kullanıldığında çıktı için neredeyse her zaman optimal değildir.

// both elements = dot(x,y)
__m128d dot1(__m256d x, __m256d y) {
    __m256d xy = _mm256_mul_pd(x, y);

    __m128d xylow  = _mm256_castps256_pd128(xy);   // (__m128d)cast isn't portable
    __m128d xyhigh = _mm256_extractf128_pd(xy, 1);
    __m128d sum1 =   _mm_add_pd(xylow, xyhigh);

    __m128d swapped = _mm_shuffle_pd(sum1, sum1, 0b01);   // or unpackhi
    __m128d dotproduct = _mm_add_pd(sum1, swapped);
    return dotproduct;
}

Sadece bir nokta ürüne ihtiyacınız varsa, bu, @ hirschhornsalz'ın tek-vektör yanıtından Intel'deki 1 shuffle uop'a göre daha iyidir ve AMD Jaguar / Bulldozer-aile / Ryzen'de daha büyük bir kazanç elde etmek yerine 128b'ye kadar daraltır. 256b malzeme demet. AMD, 256bps'yi iki 128b uops'a böler.


Kullanmaya değer olabilir hadd 2 farklı giriş vektörü ile kullandığınız yere paralel 2 veya 4 noktalı ürün yapmak gibi durumlarda. Norbert en dot sonuçların paketlenmesini istiyorsanız, vektörlerin iki çiftinin en uygun görüneceği anlamına gelir. AVX2 ile bile daha iyisini yapmanın bir yolunu görmüyorum vpermpd bir şerit geçişi shuffle olarak.

Tabii ki gerçekten daha büyük bir tane istiyorsan dot (8 veya daha fazla doubles), dikey kullan add (saklamak için birden fazla akümülatörle) vaddps gecikme süresi) ve sonunda yatay toplama yapmak.  Ayrıca kullanabilirsiniz fma mümkün ise.


haddpd dahili olarak karıştırır xy ve zw birlikte iki farklı yol ve bunu dikey olarak besler addpdve yine de elimizdeki şey bu. Eğer saklarsak xy ve zwAyrı, bir nokta ürün (ayrı kayıtlarda) almak için her biri için 2 shuffle + 2 ekler gerekir. Böylece onları bir araya getirerek hadd İlk adım olarak, toplam karma miktarını, yalnızca eklerde ve toplam uop sayısında kaydediyoruz.

/*  Norbert's version, for an Intel CPU:
    __m256d temp = _mm256_hadd_pd( xy, zw );   // 2 shuffle + 1 add
    __m128d hi128 = _mm256_extractf128_pd( temp, 1 ); // 1 shuffle (lane crossing, higher latency)
    __m128d dotproduct = _mm_add_pd( (__m128d)temp, hi128 ); // 1 add
     // 3 shuffle + 2 add
*/

Ama AMD için vextractf128 çok ucuz ve 256b hadd 128b kadar 2x maliyeti haddHer bir 256b ürününü ayrı olarak 128b'ye kadar daraltmak ve sonra 128b'lik bir hadd ile birleştirmek anlamlı olabilir.

Aslında, göre Agner Fog'ın masaları, haddpd xmm,xmm Ryzen'de 4 uops. (Ve 256b ymm sürümü 8 uops). Yani aslında 2x kullanmak daha iyidir vshufpd + vaddpd Bu veriler doğruysa, Ryzen üzerinde manuel olarak. Bu olmayabilir: Onun Piledriver onun verileri 3 Uop vardır haddpd xmm,xmmve bir bellek işleneni ile sadece 4 uops. Yapamayacakları benim için mantıklı değil. hadd sadece 3 (veya ymm için 6) olarak.


4 yapmak için dotbir sonuç içine doldurulmuş sonuçlar __m256dSorunun tam olarak sorulması, hirschhornsalz'ın yanıtının Intel CPU'lar için çok iyi olduğunu düşünüyorum. Çok dikkatli çalışmadım, ancak hadd iyidir. vperm2f128 Intel üzerinde etkilidir (ancak AMD'de oldukça kötüdür: Ryzen'de 8c'lik bir verim ile 8 uop).


2
2017-11-22 23:24