Neden 2048x2048'de 2047x2047 dizi çarpımına göre çok büyük performans artışı var?


127

Daha önce MATLAB matris çarpımında neden bu kadar hızlı? Bölümünde belirtildiği gibi bazı matris çarpım karşılaştırması yapıyorum.

Şimdi başka bir sorun var, iki 2048x2048 matrisi çarparken, C # ile diğerleri arasında büyük bir fark var. Sadece 2047x2047 matrislerini çarpmaya çalıştığımda normal görünüyor. Karşılaştırma için başkalarını da ekledim.

1024x1024 - 10 saniye.

1027x1027 - 10 saniye.

2047x2047 - 90 saniye.

2048x2048 - 300 saniye.

2049x2049 - 91 saniye. (Güncelleme)

2500x2500 - 166 saniye

Bu, 2k'ye 2k durumu için üç buçuk dakikalık farktır.

2dim dizileri kullanarak

//Array init like this
int rozmer = 2048;
float[,] matice = new float[rozmer, rozmer];

//Main multiply code
for(int j = 0; j < rozmer; j++)
{
   for (int k = 0; k < rozmer; k++)
   {
     float temp = 0;
     for (int m = 0; m < rozmer; m++)
     {
       temp = temp + matice1[j,m] * matice2[m,k];
     }
     matice3[j, k] = temp;
   }
 }

23
Bu, ileri düzey bir C programlama veya OS Tasarım sınıfı için harika bir sınav sorusu olabilir ;-)
Dana the Sane,

Hem çok boyutlu [,] hem de pürüzlü [] [] dizilerin yanı sıra 32 ve 64 bitleri test etmeyi denediniz mi? Sadece birkaç kez test ettim ama pürüzlü sonuçlar sizin sonuçlarınızla daha uyumlu görünüyordu ancak pürüzlü 64bit yüksekti, jit'te bu durum için geçerli olan herhangi bir sezgisel tarama var mı veya önbelleğinin daha önce önerildiği gibi ilişkili olup olmadığını bilmiyorum. Bir GPGPU çözümü istiyorsanız , diğer gönderinizdeki zamanlarla rekabet etmesi gereken research.microsoft.com/en-us/projects/accelerator var .
Kris,

Biraz saf bir soru, ama iki kare matrisi çarpmada kaç işlem (toplama / çarpma) yer alıyor?
Nick T

Yanıtlar:


61

Bunun muhtemelen L2 önbelleğinizdeki çakışmalarla ilgisi vardır.

Matice1'deki önbellek eksiklikleri sorun değildir çünkü bunlara sırayla erişilir. Bununla birlikte, matice2 için tam bir sütun L2'ye uyuyorsa (yani matice2 [0, 0], matice2 [1, 0], matice2 [2, 0] ... vb'ye eriştiğinizde, hiçbir sorun çıkarılmaz) önbellek matice2 ile de özlüyor.

Şimdi, değişkeninizin bayt adresi X ise, önbelleklerin nasıl çalıştığına dair daha derine inmek için, önbellek satırından (X >> 6) ve (L - 1) olacaktır. Burada L, önbelleğinizdeki toplam önbellek satırı sayısıdır. L, her zaman 2'nin gücüdür. Altı, 2 ^ 6 == 64 bayt önbellek satırının standart boyutunda olduğu gerçeğinden gelir.

Şimdi bu ne anlama geliyor? Bu, eğer X adresime ve Y adresine sahipsem ve (X >> 6) - (Y >> 6) L ile bölünebiliyorsa (yani 2'nin büyük bir kuvveti), aynı önbellekte saklanacakları anlamına gelir.

Şimdi sorununuza geri dönersek, 2048 ile 2049 arasındaki fark nedir?

2048 bedeniniz olduğunda:

& matice2 [x, k] ve & matice2 [y, k] alırsanız, fark (& matice2 [x, k] >> 6) - (& matice2 [y, k] >> 6) 2048 * 4'e bölünebilir (boyut şamandıra). Yani 2'nin büyük bir gücü.

Bu nedenle, L2'nizin boyutuna bağlı olarak, çok sayıda önbellek satırı çakışması yaşarsınız ve bir sütunu depolamak için L2'nizin yalnızca küçük bir bölümünü kullanırsınız, böylece önbelleğinizde tam sütun depolayamazsınız, bu nedenle kötü performans elde edersiniz. .

Boyut 2049 olduğunda, fark 2049 * 4'tür ki bu 2'nin gücü değildir, bu nedenle daha az çatışmaya sahip olacaksınız ve sütununuz önbelleğinize güvenli bir şekilde sığacaktır.

Şimdi bu teoriyi test etmek için yapabileceğiniz birkaç şey var:

Dizi matice2 dizinizi bu matice2 [razmor, 4096] gibi ayırın ve razmor = 1024, 1025 veya herhangi bir boyutta çalıştırın ve daha önce sahip olduklarınıza kıyasla çok kötü performans görmelisiniz. Bunun nedeni, tüm sütunları birbiriyle çakışacak şekilde zorla hizalamanızdır.

Sonra matice2'yi [razmor, 4097] deneyin ve herhangi bir boyutta çalıştırın ve çok daha iyi performans görmelisiniz.


Son 2 paragrafınızda bir hata mı yaptınız? Her iki deneme de tamamen aynı. :)
Xeo

Önbellek ilişkilendirilebilirliği de bir rol oynar.
Ben Jackson

20

Muhtemelen bir önbelleğe alma etkisi. İkinin büyük katları olan matris boyutları ve aynı zamanda ikinin gücü olan bir önbellek boyutu ile, L1 önbelleğinizin yalnızca küçük bir bölümünü kullanarak işleri çok yavaşlatabilirsiniz. Saf matris çarpımı genellikle verileri önbelleğe alma ihtiyacı ile sınırlandırılır. Döşeme (veya önbellekten habersiz algoritmalar) kullanan optimize edilmiş algoritmalar, L1 önbelleğinden daha iyi yararlanmaya odaklanır.

Diğer çiftleri (2 ^ n-1,2 ^ n) zamanlarsanız, benzer etkiler göreceksiniz.

Daha tam olarak açıklamak gerekirse, matice2 [m, k] 'ye eriştiğiniz iç döngüde, muhtemelen matice2 [m, k] ve matice2 [m + 1, k] birbirlerinden 2048 * sizeof (float) kadar ofsetlidir. ve böylece L1 önbelleğindeki aynı dizine eşlenir. N-way ilişkisel bir önbellekle, bunların tümü için genellikle 1-8 önbellek konumuna sahip olursunuz. Bu nedenle, bu erişimlerin neredeyse tamamı bir L1 önbellek tahliyesini ve verilerin daha yavaş bir önbellekten veya ana bellekten alınmasını tetikleyecektir.


+1. Olası görünüyor. Önbellek çağrışımı konusunda dikkatli olunmalıdır.
Macke

16

Bunun cpu önbelleğinizin boyutuyla bir ilgisi olabilir. Matris matrisinin 2 satırı uymuyorsa, RAM'den öğeler arasında geçiş yaparken zaman kaybedersiniz. Ekstra 4095 elemanlar, sıraların oturmasını önlemek için yeterli olabilir.

Sizin durumunuzda, 2047 2d matrisler için 2 satır, 16KB bellek içinde yer alır (32 bit türleri varsayılarak). Örneğin, 64KB'lik bir L1 önbelleğiniz (veri yolundaki cpu'ya en yakın) varsa, o zaman önbelleğe en az 4 satır (2047 * 32) sığdırabilirsiniz. Daha uzun satırlarda, satır çiftlerini 16KB'nin ötesine iten herhangi bir dolgu gerekiyorsa, işler karışmaya başlar. Ayrıca, önbelleği her 'kaçırdığınızda', başka bir önbellekten veya ana bellekten veri değiştirmek işleri geciktirir.

Tahminimce, farklı boyutlu matrislerle gördüğünüz çalışma sürelerindeki varyans, işletim sisteminin mevcut önbelleği ne kadar etkili bir şekilde kullanabileceğinden etkileniyor (ve bazı kombinasyonlar sadece sorunludur). Tabii ki bu benim açımdan büyük bir basitleştirme.


2
ancak 16.7 MB CPU önbelleğine sahip olma olasılığı çok düşük
Marino Šimić

Sonuçları 2049x2049 - 91 saniye olarak güncelledim. "Önbellek sorunu" ise, bu yine de 300+ olması gerekmez mi?
Wolf

@Marino cevap bunu hesaba katacak şekilde güncellendi.
Dana the Sane

1
Bu açıklamaların hiçbirinin sorunu ortaya çıkaran çeşitli ve seyrek boyutlarla ilgili yeni ayrıntıları yeterince ele alamayacağını düşünüyorum, diğerleri arasında etkilenmemiş.
Ken Rockot

2
Bu açıklamanın doğru olduğunu sanmıyorum. Sorun, boyut 2'nin gücü olduğunda önbellek hattı çakışmaları nedeniyle önbellek kapasitesinin tam olarak kullanılmamasıdır. Ayrıca işletim sisteminin önbelleklerle hiçbir ilgisi yoktur, çünkü neyin önbelleğe alınacağına ve neyin çıkarılacağına karar veren işletim sistemi değildir, hepsi bu. donanımda. İşletim sisteminin veri hizalamayla bir ilgisi vardır, ancak bu durumda önemli olan C # 'nın verileri nasıl tahsis etmeye karar verdiği ve 2B dizinin bellekte nasıl temsil edileceğiyle ilgilidir, işletim sisteminin bununla hiçbir ilgisi yoktur.
zviadm


5

Zamanın daha büyük boyutlarda düştüğü göz önüne alındığında, özellikle sorunlu matris boyutları için 2'nin katlarıyla önbellek çatışmaları olması daha olası olmaz mıydı? Önbelleğe alma sorunları konusunda uzman değilim, ancak burada önbellekle ilgili performans sorunları hakkında mükemmel bilgiler .


Önbellek çağrışımına ilişkin bağlantının 5. bölümü özellikle geçerli görünmektedir.
Dana the Sane

4

matice2Diziye dikey olarak erişirken , önbelleğe çok daha fazla girip çıkacaktır. Diziyi çapraz olarak yansıtırsanız, [k,m]yerine kullanarak erişebilmeniz [m,k]için kod çok daha hızlı çalışacaktır.

Bunu 1024x1024 matrisler için test ettim ve yaklaşık iki kat daha hızlı. 2048x2048 matrisler için yaklaşık on kat daha hızlıdır.


Bu, 2049'un neden 2048'den daha hızlı olduğunu açıklamıyor.
Macke

@Macke: Bunun nedeni, bellek önbelleğinde bazı sınırlamaları aşması, böylece çok daha fazla önbellek kaçırma olması.
Guffa

Neden olumsuz oy? Yanlış olduğunu düşündüğünüz şeyi söylemezseniz, cevabı iyileştiremez.
Guffa

Herhangi bir açıklama olmadan başka bir olumsuz oy ... En çok oy alan cevaplar gibi, cevabımda çok az "muhtemelen", "tahmin et" ve "olmalı" mı var ...?
Guffa

4

Önbellek Aliasing

Veya önbellek atma bir terim .

Önbellekler, düşük sıralı bitlerle indeksleyerek ve yüksek sıralı bitlerle etiketleyerek çalışır.

Önbelleğinizin 4 kelimeye sahip olduğunu ve matrisinizin 4 x 4 olduğunu imgelemek. Bir sütuna erişildiğinde ve satır uzunluğunun herhangi bir kuvveti olduğunda, bellekteki her sütun öğesi aynı önbellek öğesi ile eşleşecektir.

İki artı bir gücü aslında bu problem için optimumdur. Her yeni sütun öğesi, tıpkı satıra göre erişiliyormuş gibi sonraki önbellek yuvasıyla eşleşecektir.

Gerçek hayatta, bir etiket, arka arkaya birkaç bitişik öğeyi önbelleğe alacak sırayla artan birden çok adresi kapsar. Her yeni satırın eşleştiği grubu kaydırarak, sütunda çapraz geçiş yapmak önceki girişin yerini almaz. Bir sonraki sütun geçildiğinde, tüm önbellek farklı satırlarla doldurulacak ve önbelleğe sığan her satır bölümü birkaç sütun için isabet edecektir.

Önbellek DRAM'den çok daha hızlı olduğu için (çoğunlukla çip üzerinde olması nedeniyle) isabet oranı her şeydir.


2

Önbellek boyutu sınırına ulaşmış gibi görünüyorsunuz veya belki de zamanlamalarınızda bazı tekrarlanabilirlik sorunları yaşıyorsunuz.

Sorun ne olursa olsun, matris çarpımını kendiniz C # ile yazmamalı ve bunun yerine BLAS'ın optimize edilmiş bir sürümünü kullanmalısınız. Herhangi bir modern makinede bu boyuttaki matris bir saniyenin altında çarpılmalıdır.


1
BLAS'ın farkındayım, ancak görev onu olabildiğince hızlı yapmak değil, çeşitli dillerde yazmak ve test etmekti. Bu benim için çok garip bir problem ve sonuçların neden böyle olduğunu gerçekten merak ediyorum.
Wolf

3
@Wolf, bir saniye sürmesi gereken bir şeyin 90 saniye mi yoksa 300 saniye mi sürdüğü konusunda heyecanlanmakta zorlanırdım.
David Heffernan

4
Bir şeyin nasıl çalıştığını öğrenmenin en iyi yolu, onu kendiniz yazmak ve uygulamanızı nasıl geliştirebileceğinizi görmektir; Bu (umarım) Wolf'un yaptığı şeydir.
Callum Rogers

@Callum Rogers, kabul etti. Dosya kopyalama işlemlerinde arabellek boyutlarının önemini bu şekilde öğrendim.
Kelly S. Fransız

1

Önbellek hiyerarşisini etkili bir şekilde kullanmak çok önemlidir. Çok boyutlu dizilerin güzel bir düzenlemede verilere sahip olduğundan emin olmanız gerekir, bu da döşeme ile başarılabilir . Bunu yapmak için 2D diziyi bir indeksleme mekanizmasıyla birlikte 1D dizisi olarak depolamanız gerekir. Geleneksel yöntemdeki sorun, aynı satırdaki iki bitişik dizi öğesinin bellekte yan yana olmasına rağmen, aynı sütundaki iki bitişik öğenin bellekteki W öğeleriyle ayrılmasıdır ; burada W , sütun sayısıdır. . Döşeme, on faktör performans farkı yaratabilir.


Hmm - yine de 2D olarak bildirilen bir dizi (float [,] matice = new float [rozmer, rozmer];) RAM'de yalnızca tek boyutlu bir dizi olarak tahsis edilir ve başlık altında yapılan sıra / adım hesaplamaları. Öyleyse neden 1D olarak ilan etmek ve manuel sıra / adım hesaplamaları yapmak daha hızlı olsun? Sol'n'un, her biri büyük dizinin sığamayacağı önbelleğe sığabilecek daha küçük parçalar dizisi olarak büyük bir dizi ayırdığını mı söylüyorsunuz?
Eric M

1
Kitaplığınız veya kullandığınız her hangi bir araç döşeme yapıyorsa, buna ihtiyacınız yoktur. Ancak, örneğin C / C ++ 'da geleneksel bir 2D dizi kullanırsanız, döşeme performansı artıracaktır.
Arlen

0

Bunun " Ardışık Sel " denen bir şeyin sonucu olduğundan şüpheleniyorum . Bu, önbellek boyutundan biraz daha büyük olan nesnelerin listesi boyunca döngü yapmaya çalıştığınızdır, bu nedenle bir listeye (diziye) yönelik her bir istek, ramdan yapılmalıdır ve tek bir önbellek elde edemezsiniz. çarptı.

Sizin durumunuzda, dizileriniz 2048 indekslerinizde 2048 kez döngü yapıyorsunuz, ancak yalnızca 2047 için alanınız var (muhtemelen dizi yapısından kaynaklanan bazı ek yükler nedeniyle), bu nedenle bir dizi konumuna her eriştiğinizde, bu dizi konumunu alması gerekir. koçtan. Daha sonra önbellekte saklanır, ancak tekrar kullanılmadan hemen önce atılır. Yani önbellek aslında işe yaramaz ve çok daha uzun bir yürütme süresine yol açar.


1
Yanlış. 2049, 2048'den daha hızlı, bu da iddianızı yalanlıyor.
Macke

@Macke: Bu oldukça mümkün. Ama bir var hafif onun işlemcide kullanılan önbellek politikası hala bu kararın hızla yapmak gösterme olasılığı. Pek olası değil, ama düşünülemez de değil.
Automatico
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.