Performansı artırmak için CPU önbelleğini en iyi kullanan bir yazma kodu nasıl kullanılır?


159

Bu öznel bir soru gibi gelebilir, ama aradığım şey bununla ilgili karşılaşabileceğiniz belirli örneklerdir.

  1. Kod, önbellek etkin / önbellek dostu nasıl yapılır (daha fazla önbellek isabet, mümkün olduğunca az önbellek kaçırma)? Her iki açıdan bakıldığında, veri önbelleği ve program önbelleği (yönerge önbelleği), yani bir kişinin kodundaki, veri yapıları ve kod yapılarıyla ilgili olan şeylerin, önbelleği etkili hale getirmeye özen göstermesi gerekir.

  2. Kod önbelleğini etkili hale getirmek için bir kişinin kullanması / kaçınması gereken belirli bir veri yapısı var mı veya bu yapının üyelerine erişmenin belirli bir yolu var mı?

  3. Herhangi bir program yapısı (eğer, switch, break, goto, ...), kod akışı (eğer bir iç, bir iç için, vb ... gibi) varsa bu konuda takip / kaçınılması gerekir mi?

Genel olarak önbellek verimli kod yapma ile ilgili bireysel deneyimleri duymak için sabırsızlanıyorum. Herhangi bir programlama dili (C, C ++, Assembly, ...), herhangi bir donanım hedefi (ARM, Intel, PowerPC, ...), herhangi bir işletim sistemi (Windows, Linux, S ymbian, ...) vb. .

Çeşitlilik onu derinlemesine anlamaya daha iyi yardımcı olacaktır.


1
Bir giriş olarak bu konuşma iyi bir genel bakış sunar youtu.be/BP6NxVxDQIs
schoetbi

Yukarıdaki kısaltılmış URL artık çalışmıyor gibi görünüyor, bu konuşmanın tam URL'si: youtube.com/watch?v=BP6NxVxDQIs
Abhinav Upadhyay

Yanıtlar:


119

Önbellek, CPU'nun bir bellek isteğinin yerine getirilmesini bekleyen bekleme süresini azaltmak (bellek gecikmesinden kaçınmak ) ve ikinci bir etki olarak, muhtemelen aktarılması gereken toplam veri miktarını azaltmak (korumak için) bellek bant genişliği ).

Bellek getirme gecikmesinden muzdarip olma teknikleri genellikle dikkate alınması gereken ilk şeydir ve bazen uzun bir yol kat eder. Sınırlı bellek bant genişliği, özellikle çok sayıda iş parçacığının bellek veri yolunu kullanmak istediği çok çekirdekli ve çok iş parçacıklı uygulamalar için de sınırlayıcı bir faktördür. Farklı bir dizi teknik, ikinci sorunun çözümüne yardımcı olur.

Uzamsal konumun iyileştirilmesi , her önbellek satırının bir önbelleğe eşlendikten sonra tam olarak kullanıldığından emin olmanız anlamına gelir. Çeşitli standart ölçütlere baktığımızda, şaşırtıcı bir büyük kısmın önbellek hatları çıkarılmadan önce getirilen önbellek hatlarının% 100'ünü kullanamadığını gördük.

Önbellek kullanımının iyileştirilmesi üç açıdan yardımcı olur:

  • Önbellekte daha yararlı veriler sığdırma eğilimindedir ve bu da etkili önbellek boyutunu artırır.
  • Aynı önbellek satırına daha kullanışlı veriler sığdırma eğilimindedir ve istenen verilerin önbellekte bulunma olasılığını artırır.
  • Daha az getirme olacağından bellek bant genişliği gereksinimlerini azaltır.

Ortak teknikler:

  • Daha küçük veri türleri kullanın
  • Hizalama deliklerinden kaçınmak için verilerinizi düzenleyin (yapı üyelerinizi boyutu küçülterek sıralamak bir yoludur)
  • Standart dinamik bellek ayırıcıya dikkat edin, bu da delikler açarak ısındığında verilerinizi belleğe yayabilir.
  • Sıcak döngülerde tüm bitişik verilerin gerçekten kullanıldığından emin olun. Aksi takdirde, sıcak döngülerin sıcak veri kullanması için veri yapılarını sıcak ve soğuk bileşenlere ayırmayı düşünün.
  • düzensiz erişim modelleri gösteren algoritmalardan ve veri yapılarından kaçının ve doğrusal veri yapılarını destekleyin.

Bellek gecikmesini gizlemenin önbellek kullanmaktan başka yolları olduğunu da not etmeliyiz.

Modern CPU'lar genellikle bir veya daha fazla donanım ön getiricisine sahiptir . Bir önbellekte özlüyorlar üzerinde eğitiyorlar ve düzenlilikleri tespit etmeye çalışıyorlar. Örneğin, sonraki önbellek satırlarını birkaç özledikten sonra, hw prefetcher, uygulamanın ihtiyaçlarını önceden tahmin ederek önbellek satırlarını önbelleğe almaya başlar. Düzenli bir erişim düzeniniz varsa, donanım prefetcher genellikle çok iyi bir iş çıkarır. Programınız düzenli erişim kalıpları göstermiyorsa, önceden getirme talimatlarını kendiniz ekleyerek işleri geliştirebilirsiniz .

Talimatları önbellekte her zaman özleyenler birbirine yakın olacak şekilde yeniden gruplandırılır, CPU bazen bu getirileri üst üste bindirir, böylece uygulama sadece bir gecikme isabetini sürdürür ( Bellek seviyesi paralellik ).

Toplam bellek veri yolu basıncını azaltmak için, geçici konum denilen adrese değinmeye başlamanız gerekir . Bu, yine de önbellekten çıkarılmadığı halde verileri yeniden kullanmanız gerektiği anlamına gelir.

Aynı verilere dokunan döngüler birleştirilir ( döngü füzyonu ) ve döşeme veya engelleme olarak bilinen yeniden yazma tekniklerinin kullanılması, bu fazladan bellek getirmelerini önlemek için çaba gösterir.

Bu yeniden yazma alıştırması için bazı temel kurallar olsa da, programın anlambilimini etkilemediğinizden emin olmak için genellikle döngü taşınan veri bağımlılıklarını dikkatlice düşünmeniz gerekir.

Bunlar, ikinci iş parçasını ekledikten sonra genellikle verim artışlarının çoğunu görmeyeceğiniz çok çekirdekli dünyada gerçekten işe yarayan şeydir.


5
Çeşitli standart ölçütlere baktığımızda, şaşırtıcı bir büyük kısmın önbellek hatları çıkarılmadan önce getirilen önbellek hatlarının% 100'ünü kullanamadığını gördük. Ne tür profilleme araçlarının size bu tür bilgiler verdiğini ve nasıl olduğunu sorabilir miyim?
Dragon Energy

"Hizalama deliklerinden kaçınmak için verilerinizi düzenleyin (yapı üyelerinizi boyutu küçülterek sıralamanın bir yolu vardır") - neden derleyici bunu optimize etmiyor? neden derleyici her zaman "boyutu küçülterek üyeleri sıralayamaz"? üyeleri sıralanmamış tutmanın avantajı nedir?
javapowered

Kökenleri bilmiyorum, ancak birincisi, üye yapısı bayt bayt baytını web üzerinden göndermek isteyebileceğiniz ağ iletişiminde çok önemlidir.
Kobrar

1
@javapowered Derleyici, dile bağlı olarak bunu yapabilir, ancak bunlardan herhangi birinin yapılıp yapılmadığından emin değilim. C'de yapamamanızın nedeni, üyelerin isminden ziyade temel adres + ofset ile adreslenmesinin mükemmel bir şekilde geçerli olmasıdır, yani üyelerin yeniden düzenlenmesi programı tamamen kıracaktır.
Dan Bechard

56

Bunun daha fazla yanıtı olmadığına inanamıyorum. Her neyse, klasik bir örnek, "içten dışa" çok boyutlu bir diziyi yinelemektir:

pseudocode
for (i = 0 to size)
  for (j = 0 to size)
    do something with ary[j][i]

Bunun önbellek verimsiz olmasının nedeni, modern CPU'ların tek bir bellek adresine eriştiğinizde önbellek satırını ana bellekten "yakın" bellek adresleriyle yükleyecek olmasıdır. İç döngüdeki dizideki "j" (dış) satırları tekrarlıyoruz, bu nedenle iç döngüdeki her yolculuk için önbellek satırı temizlenip [ j] [i] girişi. Bu eşdeğer olarak değiştirilirse:

for (i = 0 to size)
  for (j = 0 to size)
    do something with ary[i][j]

Çok daha hızlı çalışacaktır.


9
kolejde matris çarpımı ile ilgili bir ödevimiz vardı. Önce "sütunlar" matrisinin bir devralınmasının daha hızlı olduğu ve bu kesin sebepten dolayı satırları sütunlarla değil satırlarla satırlarla çarpmanın daha hızlı olduğu ortaya çıktı.
ykaganovich

11
aslında, modern derleyicilerin çoğu bunu kendi başına çözebilir (optimizasyonlar açıkken)
Ricardo Nolde

1
@ykaganovich Ulrich Dreppers makalesinde de örnek: lwn.net/Articles/255364
Simon Stender

Bunun her zaman doğru olduğundan emin değilim - tüm dizi L1 önbelleğine (genellikle 32k!) Sığarsa, her iki sipariş de önbellek isabet ve özledim aynı sayıda olacaktır. Belki de bellek ön-getirme bazı etkileri olabilir sanırım. Elbette düzeltildiğim için mutluyum.
Matt Parkins

Siparişin önemi yoksa bu kodun ilk sürümünü kim seçer?
silver_rocket

45

Temel kurallar aslında oldukça basittir. Zorlaştığı yer, kodunuza nasıl uygulanacağıdır.

Önbellek iki ilke üzerinde çalışır: Geçici konum ve mekansal konum. Birincisi, kısa bir süre önce belirli bir veri yığını kullandıysanız, muhtemelen yakında tekrar ihtiyacınız olacağı fikridir. İkincisi, verileri yakın zamanda X adresinde kullandıysanız, muhtemelen yakında X + 1 adresine ihtiyacınız olacaktır.

Önbellek, en son kullanılan veri yığınlarını hatırlayarak bunu karşılamaya çalışır. Genellikle 128 bayt büyüklüğünde önbellek satırlarıyla çalışır, bu nedenle yalnızca tek bir bayta ihtiyacınız olsa bile, onu içeren önbellek satırının tamamı önbelleğe alınır. Daha sonra aşağıdaki bayta ihtiyacınız varsa, zaten önbellekte olacaktır.

Bu da, kendi kodunuzun her zaman bu iki konum biçiminden mümkün olduğunca yararlanmasını isteyeceğiniz anlamına gelir. Hafızanın her yerine atlamayın. Küçük bir alanda yapabildiğiniz kadar çok iş yapın ve sonra bir sonrakine geçin ve orada olabildiğince çok iş yapın.

Basit bir örnek, 1800'ün cevabının gösterdiği 2D dizi geçişidir. Her seferinde bir satır geçerseniz, belleği sırayla okursunuz. Sütun olarak yaparsanız, bir girişi okur, ardından tamamen farklı bir yere (bir sonraki satırın başlangıcına) atlar, bir girişi okur ve tekrar atlarsınız. Ve nihayet ilk satıra döndüğünüzde, artık önbellekte olmayacak.

Aynı kural kod için de geçerlidir. Atlamalar veya dallar daha az verimli önbellek kullanımı anlamına gelir (çünkü talimatları sırayla okumuyorsunuz, ancak farklı bir adrese atlıyorsunuz). Tabii ki, küçük if ifadeleri muhtemelen hiçbir şeyi değiştirmez (sadece birkaç bayt atlarsınız, bu yüzden yine de önbelleğe alınmış bölgenin içinde kalırsınız), ancak işlev çağrıları genellikle tamamen farklı bir önbelleğe alınamayan adres. Son zamanlarda çağrılmadığı sürece.

Öğretim önbellek kullanımı genellikle bir sorundan çok daha azdır. Genellikle endişelenmeniz gereken şey veri önbelleğidir.

Bir yapıda veya sınıfta, tüm üyeler bitişik olarak düzenlenir, bu da iyidir. Bir dizide, tüm girişler de bitişik olarak düzenlenir. Bağlantılı listelerde, her düğüm tamamen farklı bir konuma atanır, bu da kötüdür. İşaretçiler genel olarak ilgisiz adreslere işaret etme eğilimindedir, bu da eğer başvuruyu geri çekerseniz önbellek kaçmasına neden olur.

Birden fazla çekirdeği kullanmak istiyorsanız, gerçekten ilginç olabilir, genellikle olduğu gibi, L1 önbelleğinde aynı anda yalnızca bir CPU'nun herhangi bir adresi olabilir. Bu nedenle, her iki çekirdek de sürekli olarak aynı adrese erişirse, adres üzerinde savaşırken sürekli önbellek kaybına neden olur.


4
+1, iyi ve pratik tavsiyeler. Bir ek: Zaman yeri ve boşluk yeri birleşimi, örneğin matris ops için, bunların tamamen bir önbellek satırına veya satırları / sütunları önbellek satırlarına uyan daha küçük matrislere bölünmesinin önerilebileceğini göstermektedir. Bunu multidim'in görselleştirilmesi için yaptığımı hatırlıyorum. veri. Bu pantolon ciddi bir tekme sağladı. Önbelleğin birden fazla 'satır' içerdiğini hatırlamak iyidir;)
AndreasT

1
Sadece 1 CPU'nun L1 önbelleğinde belirli bir adrese sahip olabileceğini söylüyorsunuz - adres yerine önbellek satırları demek istediğinizi varsayalım. Ayrıca CPU'lardan en az biri yazma yaparken yanlış paylaşım problemleri duydum, ama her ikisi de sadece okuma yapıyorsa değil. Peki 'erişim' ile yazmak demek istiyorsun?
Joseph Garvin

2
@JosephGarvin: evet, demek istediğim yazıyor. Haklısınız, birden fazla çekirdek L1 önbelleklerinde aynı anda aynı önbellek satırlarına sahip olabilir, ancak bir çekirdek bu adreslere yazdığında, diğer tüm L1 önbelleklerinde geçersiz kılınır ve daha sonra yapmadan önce yeniden yüklemeleri gerekir onunla herhangi bir şey. Kesin olmayan (yanlış) ifadeler için özür dilerim. :)
jalf

44

Bellek ve yazılımın nasıl etkileşime girdiğiyle ilgileniyorsanız, her programcının Ulrich Drepper'ın belleği hakkında bilmesi gereken 9 bölümlü makaleyi okumanızı öneririz . 104 sayfalık PDF olarak da mevcuttur .

Bu soruyla özellikle ilgili bölümler Bölüm 2 (CPU önbellekleri) ve Bölüm 5 (Programcıların yapabilecekleri - önbellek optimizasyonu) olabilir.


16
Makaledeki ana noktaların bir özetini eklemelisiniz.
Azmisov

Harika bir okuma, ancak burada belirtilmesi gereken bir başka kitap Hennessy, Patterson, Bilgisayar Mimarisi, Bir Niceliksel Yaklaşımdır ve bugünün 5. baskısında mevcuttur.
Haymo Kutschbach

15

Veri erişim kalıplarının yanı sıra, önbellek dostu kodda önemli bir faktör veri boyutudur . Daha az veri, daha fazlasının önbelleğe sığdığı anlamına gelir.

Bu esas olarak belleğe hizalanmış veri yapıları ile ilgili bir faktördür. "Konvansiyonel" bilgelik, veri yapılarının kelime sınırlarında hizalanması gerektiğini, çünkü CPU yalnızca tüm kelimelere erişebildiğini ve bir kelime birden fazla değer içeriyorsa, fazladan iş yapmanız (basit yazma yerine okuma-değiştirme-yazma) . Ancak önbellekler bu argümanı tamamen geçersiz kılabilir.

Benzer şekilde, bir Java boole dizisi, her bir değer için ayrı ayrı değerler üzerinde doğrudan çalışmayı sağlamak için tüm bir bayt kullanır. Gerçek bitleri kullanırsanız veri boyutunu 8 kat azaltabilirsiniz, ancak daha sonra ayrı değerlere erişim çok daha karmaşık hale gelir ve bit kaydırma ve maske işlemleri gerektirir ( BitSetsınıf bunu sizin için yapar). Ancak, önbellek efektleri nedeniyle, bu dizi büyük olduğunda bir boolean [] kullanmaktan önemli ölçüde daha hızlı olabilir. IIRC I bir zamanlar bu şekilde 2 veya 3 faktör hızlandırmayı başardı.


9

Önbellek için en etkili veri yapısı bir dizidir. CPU'lar önbellek satırlarının tamamını (genellikle 32 bayt veya daha fazla) bir kerede ana bellekten okurken veri yapınız sırayla düzenlenirse, önbellekler en iyi şekilde çalışır.

Belleğe rasgele sırayla erişen herhangi bir algoritma, önbellekleri çöker çünkü rastgele erişilen belleği karşılamak için her zaman yeni önbellek satırlarına ihtiyaç duyar. Öte yandan, bir dizi boyunca sırayla çalışan bir algoritma en iyisidir çünkü:

  1. CPU'ya önceden okuma şansı verir, örneğin spekülatif olarak daha sonra erişilecek önbelleğe daha fazla bellek koyun. Bu ileri okuma, büyük bir performans artışı sağlar.

  2. Geniş bir dizi üzerinde sıkı bir döngü çalıştırmak, CPU'nun döngüde yürütülen kodu önbelleğe almasını sağlar ve çoğu durumda harici bellek erişimini engellemek zorunda kalmadan tamamen önbellekten bir algoritma çalıştırmanıza izin verir.


@Grover: 2. noktanız hakkında, eğer insidea sıkı döngü varsa, her döngü sayısı için bir işlev çağrılırsa, o zaman yeni kodu getirecek ve bir önbellek kaçırmasına neden olacak, bunun yerine u işlevi bir for döngüsü kendisi, hiçbir işlev çağrısı kodu, daha az önbellek özledim nedeniyle daha hızlı olurdu?
goldenmean

1
Evet ve hayır. Yeni işlev önbelleğe yüklenir. Yeterli önbellek alanı varsa, ikinci yinelemede önbellekte zaten bu işleve sahip olur, bu yüzden tekrar yüklemek için bir neden yoktur. Yani ilk çağrıda bir hit oldu. C / C ++ 'da derleyiciden uygun segmentleri kullanarak fonksiyonları yan yana koymasını isteyebilirsiniz.
Grover

Bir not daha: Döngüden çağırırsanız ve yeterli önbellek alanı yoksa, yeni işlev ne olursa olsun önbelleğe yüklenir. Orijinal halkanın önbellekten atılacağı bile olabilir. Bu durumda, çağrı her yineleme için en fazla üç cezaya neden olur: Biri çağrı hedefini yüklemek için diğeri döngüyü yeniden yüklemek için. Üçüncüsü, döngü kafası çağrı dönüş adresiyle aynı önbellek satırında değilse. Bu durumda, döngü kafasına atlamanın da yeni bir bellek erişimine ihtiyacı vardır.
grover

8

Bir oyun motorunda kullandığım bir örnek, verileri nesnelerden ve kendi dizilerine taşımaktı. Fiziğe maruz kalan bir oyun nesnesinin kendisine eklenmiş birçok veri olabilir. Ancak fizik güncelleme döngüsü sırasında tüm motor, konum, hız, kütle, sınırlama kutusu vb. İle ilgili verilerdi. Böylece bunların hepsi kendi dizilerine yerleştirildi ve SSE için mümkün olduğunca optimize edildi.

Böylece fizik döngüsü sırasında fizik verileri vektör matematiği kullanılarak dizi sırasıyla işlendi. Oyun nesneleri, nesne dizilerini çeşitli dizilerde dizin olarak kullanmıştır. Bir işaretçi değildi çünkü dizilerin yerlerinin değiştirilmesi gerekiyorsa işaretçiler geçersiz hale gelebilir.

Birçok yönden bu, nesne yönelimli tasarım modellerini ihlal etti, ancak aynı döngülerde çalıştırılması gereken verileri birbirine yakınlaştırarak kodu çok daha hızlı hale getirdi.

Bu örnek muhtemelen güncel değil çünkü çoğu modern oyunun Havok gibi önceden oluşturulmuş bir fizik motoru kullanmasını bekliyorum.


2
+1 Hiç güncel değil. Bu, oyun motorları için verileri organize etmenin en iyi yoludur - veri bloklarını bitişik hale getirin ve önbellek yakınlığından / yerinden yararlanmak için bir sonrakine (fizik diyelim) geçmeden önce belirli bir işlem türünü (AI deyin) gerçekleştirin referans.
Mühendis

Bu örneği birkaç hafta önce bir videoda gördüm, ancak o zamandan bu yana bağlantısını kaybettim / nasıl bulacağımı hatırlayamıyorum. Bu örneği nerede gördüğünüzü hatırlıyor musunuz?
olacak

@will: Hayır, tam olarak nerede olduğunu hatırlamıyorum.
Zan Lynx

Bu bir varlık bileşen sistemi fikridir (ECS: en.wikipedia.org/wiki/Entity_component_system ). Verileri, OOP uygulamalarının teşvik ettiği daha geleneksel yapı dizilerinden ziyade dizi yapısı olarak depolayın.
BuschnicK

7

Sadece bir gönderi üzerine dokundu, ancak süreçler arasında veri paylaşırken büyük bir sorun ortaya çıkıyor. Aynı önbellek satırını aynı anda değiştirmeye çalışan birden çok işlem yapmaktan kaçınmak istersiniz. Burada dikkat edilmesi gereken bir şey, iki bitişik veri yapısının bir önbellek satırını paylaştığı ve bir tanesinde yapılan değişikliklerin diğeri için önbellek satırını geçersiz kıldığı "yanlış" paylaşımdır. Bu, önbellek satırlarının, verileri çok işlemcili bir sistemde paylaşan işlemci önbellekleri arasında gereksiz yere ileri geri hareket etmesine neden olabilir. Bundan kaçınmanın bir yolu, veri yapılarını farklı satırlara yerleştirmek için hizalamak ve doldurmaktır.


7

Kullanıcı 1800 BİLGİ tarafından "klasik örnek" için bir açıklama (yorum için çok uzun)

İki yineleme sırası ("outter" ve "inner") için zaman farklarını kontrol etmek istedim, bu yüzden büyük bir 2D dizisi ile basit bir deney yaptım:

measure::start();
for ( int y = 0; y < N; ++y )
for ( int x = 0; x < N; ++x )
    sum += A[ x + y*N ];
measure::stop();

ve ikinci dava for ilmekleri değiştirilmiş .

Yavaş sürüm ("önce x") 0,88 sn ve daha hızlı olan sürüm 0,06 sn idi. Bu önbellekleme gücü :)

Kullandım gcc -O2ve döngüler hala optimize edilmedi . Ricardo'nun "modern derleyicilerin çoğunun bunu kendiliğinden çözebileceği" yorumu


Bunu anladığımdan emin değilim. Her iki örnekte de for döngüsünde her bir değişkene erişmeye devam ediyorsunuz. Neden biri diğerinden daha hızlı?
ed-

nasıl etkilediğini anlamak benim için sezgisel :)
Laie

@EdwardCorlew Erişildikleri sıradan kaynaklanıyor. Y-sırası daha hızlıdır çünkü verilere sırayla erişir. İlk giriş istendiğinde L1 önbelleği, int'i artı bir sonraki 15'i (64 baytlık bir önbellek satırı varsayarak) içeren bir önbellek hattının tamamını yükler, böylece sonraki 15'i bekleyen CPU durakları olmaz. -İlk sipariş daha yavaştır, çünkü erişilen eleman sıralı değildir ve muhtemelen N erişilen belleğin her zaman L1 önbelleğinin dışında olacağı kadar büyüktür ve bu nedenle her işlem durur.
Matt Parkins

4

C ++ dünyasında, bağlantılı listelerin CPU önbelleğini kolayca öldürebileceğini söyleyerek cevap verebilirim (2). Diziler mümkün olduğunda daha iyi bir çözümdür. Aynı dilin diğer diller için de geçerli olup olmadığı konusunda deneyim yoktur, ancak aynı sorunların ortaya çıkacağını hayal etmek kolaydır.


@Andrew: Yapılara ne dersiniz? Önbellek verimli mi? Önbellek etkinliğinde herhangi bir boyut kısıtlaması var mı?
goldenmean

Bir yapı, tek bir bellek bloğudur, önbelleğinizin boyutunu aşmadığı sürece bir etki görmezsiniz. Yalnızca önbellek isabetlerini göreceğiniz bir yapı (veya sınıf) koleksiyonunuz olduğunda ve koleksiyonunuzu düzenleme şeklinize bağlıdır. Bir dizi, nesneleri birbirine karşı koyar (iyi), ancak bağlantılı bir liste, adres alanınızın her yerinde, aralarında bağlantılar bulunan nesneler içerebilir, bu da önbellek performansı için açıktır.
Andrew

Bağlantılı listeleri önbelleği öldürmeden kullanmanın bir yolu, büyük listeler için en etkili olanı, kendi bellek havuzunuzu oluşturmak, yani bir büyük dizi ayırmaktır. daha sonra, bellekte tamamen farklı bir yere tahsis edilebilecek her küçük bağlı liste üyesi için 'yanlış konumlandırma (veya C ++' da yeni ') belleği ve atık yönetimi alanı yerine, bellek havuzunuzdan bellek verir, listenin üyelerini mantıksal olarak kapama olasılığını son derece artıran önbellekte birlikte olacak.
Liran Orevi

Tabii, ama std :: list <> et al. özel bellek bloklarınızı kullanmak için. Genç bir whippersnapper olduğumda kesinlikle bu yola giderdim, ama bu günlerde ... uğraşmak için çok fazla şey var.
Andrew


4

Önbellek "önbellek satırlarında" düzenlenir ve (gerçek) bellek bu boyuttaki parçalardan okunur ve buraya yazılır.

Bu nedenle, tek bir önbellek satırında bulunan veri yapıları daha verimlidir.

Benzer şekilde, bitişik bellek bloklarına erişen algoritmalar, rasgele sırada bellekte atlayan algoritmalardan daha verimli olacaktır.

Maalesef önbellek satır boyutu işlemciler arasında önemli ölçüde değişiklik göstermektedir, bu nedenle bir işlemcide en uygun veri yapısının diğerinde verimli olacağını garanti etmenin bir yolu yoktur.


şart değil. yanlış paylaşım konusunda dikkatli olun. bazen verileri farklı önbellek satırlarına bölmeniz gerekir. önbellek her zaman ne kadar etkilidir, onu nasıl kullandığınıza bağlıdır.
DAG

4

Bir kodun nasıl oluşturulacağını sormak, etkili önbellek dostu önbellek ve diğer soruların çoğu, genellikle bir programın nasıl Optimize Edileceğini sormaktır, çünkü önbellek, herhangi bir optimize edilmiş programın önbellek olan performanslar üzerinde büyük bir etkiye sahip olmasıdır. etkili önbellek dostu.

Optimizasyon hakkında okumanızı öneririm, bu sitede bazı iyi yanıtlar var. Kitaplar açısından Bilgisayar Sistemleri: Bir Programcının Perspektifi hakkında tavsiye ediyorum , önbelleğin doğru kullanımı hakkında bazı güzel metinleri olan .

(btw - bir önbellek özlüyor olabileceği kadar kötü, daha da kötüsü - bir program sabit sürücüden sayfalama yapıyorsa ...)


4

Veri yapısı seçimi, erişim kalıbı, vb.Gibi genel tavsiyelerde bir çok cevap olmuştur. Burada yazılım boru hattı adı verilen başka bir kod tasarım deseni eklemek istiyorum aktif önbellek yönetimini kullanan .

Buradaki fikir, CPU talimat boru hattı gibi diğer boru hattı tekniklerinden ödünç alınmasıdır.

Bu tip model en iyi prosedürler için geçerlidir

  1. yürütme süresi kabaca RAM erişim süresi (~ 60-70ns) ile karşılaştırılabilir olan makul çoklu alt aşamalara, S [1], S [2], S [3], ...
  2. bir grup girdi alır ve sonuç almak için yukarıda belirtilen birçok adımı uygulayın.

Sadece bir alt prosedürün olduğu basit bir durumu ele alalım. Normalde kod:

def proc(input):
    return sub-step(input))

Daha iyi performans elde etmek için, bir toplu işteki işleve birden çok giriş iletmek isteyebilirsiniz, böylece işlev çağrısı ek yükünü amorti eder ve ayrıca kod önbellek yerini artırır.

def batch_proc(inputs):
    results = []
    for i in inputs:
        // avoids code cache miss, but still suffer data(inputs) miss
        results.append(sub-step(i))
    return res

Ancak, daha önce de belirtildiği gibi, adımın yürütülmesi kabaca RAM erişim süresiyle aynı ise, kodu aşağıdaki gibi bir şeye daha da geliştirebilirsiniz:

def batch_pipelined_proc(inputs):
    for i in range(0, len(inputs)-1):
        prefetch(inputs[i+1])
        # work on current item while [i+1] is flying back from RAM
        results.append(sub-step(inputs[i-1]))

    results.append(sub-step(inputs[-1]))

Yürütme akışı şöyle görünecektir:

  1. prefetch (1) CPU'dan girişi [1] önbelleğe önceden getirmesini ister, burada ön alma talimatı P döngülerinin kendisini alır ve geri döner ve arka plan girişinde [1] R döngülerinden sonra önbelleğe ulaşır.
  2. works_on (0) 0 üzerinde soğuk özledim ve üzerinde çalışır, bu da M'yi alır
  3. prefetch (2) başka bir getirme sorunu
  4. works_on (1) P + R <= M ise, [1] girişleri bu adımdan önce önbellekte olmalıdır, bu nedenle veri önbelleğini kaçırmayın
  5. çalışma_on (2) ...

Daha fazla adım olabilir, daha sonra adımların zamanlaması ve bellek erişim gecikmesi eşleştiği sürece çok aşamalı bir boru hattı tasarlayabilirsiniz, çok az kod / veri önbellek özlemi çekersiniz. Bununla birlikte, adımların doğru gruplandırılmasını ve önceden getirme süresini bulmak için bu sürecin birçok deneyle ayarlanması gerekir. Gerekli çabası nedeniyle, yüksek performanslı veri / paket akışı işlemede daha fazla benimsenmeyi görür. DPDK QoS Enqueue boru hattı tasarımında iyi bir üretim kodu örneği bulunabilir: http://dpdk.org/doc/guides/prog_guide/qos_framework.html Bölüm 21.2.4.3. Boru Hattı Enqueue.

Daha fazla bilgi bulunabilir:

https://software.intel.com/en-us/articles/memory-management-for-optimal-performance-on-intel-xeon-phi-coprocessor-alignment-and

http://infolab.stanford.edu/~ullman/dragon/w06/lectures/cs243-lec13-wei.pdf


1

En az boyut almak için programınızı yazın. Bu nedenle GCC için -O3 optimizasyonlarını kullanmak her zaman iyi bir fikir değildir. Daha büyük bir boyut alır. Genellikle, -Os, -O2 kadar iyidir. Her şey olsa kullanılan işlemciye bağlıdır. YMMV.

Her seferinde küçük veri parçalarıyla çalışın. Bu nedenle, veri seti büyükse, daha az verimli bir sıralama algoritması hızlı sıralamaya göre daha hızlı çalışabilir. Daha büyük veri kümelerinizi daha küçük olanlara bölmenin yollarını bulun. Diğerleri bunu önerdi.

Geçici / uzamsal konum talimatlarından daha iyi yararlanmanıza yardımcı olmak için, kodunuzun birleştirmeye nasıl dönüştürüldüğünü incelemek isteyebilirsiniz. Örneğin:

for(i = 0; i < MAX; ++i)
for(i = MAX; i > 0; --i)

İki döngü, yalnızca bir dizi üzerinden ayrıştırılsa bile farklı kodlar üretir. Her durumda, sorunuz mimariye özeldir. Bu nedenle, önbellek kullanımını sıkı bir şekilde kontrol etmenin tek yolu, donanımın nasıl çalıştığını anlamak ve kodunuzu bunun için optimize etmektir.


İlginç bir nokta. İleriye dönük önbellekler, bir döngü / bellekten geçiş yönüne göre varsayımlar yapar mı?
Andrew

1
Spekülatif veri önbellekleri tasarlamanın birçok yolu vardır. Adım tabanlı olanlar veri erişiminin 'mesafesini' ve 'yönünü' ölçer. İçerik tabanlı olanlar işaretçi zincirlerini takip eder. Onları tasarlamanın başka yolları da var.
sybreon

1

Yapınızı ve alanlarınızı hizalamanın yanı sıra, eğer yığın ayrılmışsa yapınız hizalı tahsisleri destekleyen ayırıcılar kullanmak isteyebilirsiniz; _aligned_malloc (sizeof (DATA), SYSTEM_CACHE_LINE_SIZE) gibi; aksi takdirde rasgele yanlış paylaşımınız olabilir; Windows'ta varsayılan öbeğin 16 baytlık bir hizalamaya sahip olduğunu unutmayın.

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.