Her Programcı Bellek Hakkında Ne Bilmeli?


164

Ulrich Drepper'ın 2007'deki Her Programcının Bellek Hakkında Bilmesi Gerekenler'in hala ne kadar geçerli olduğunu merak ediyorum . Ayrıca 1.0 veya errata'dan daha yeni bir sürüm bulamadım.


1
birisi bu makaleyi mobi formatında bir yere indirip indiremeyeceğimi biliyor mu? "pdf" zum / biçimlendirme ile ilgili sorunlar nedeniyle okunması çok zor
javapowered 3:13

1
Mobi değil, ama LWN kağıdı bir telefonda / tablette okunması daha kolay bir dizi makale olarak yayınladı. İlki lwn.net/Articles/250967
Nathan

Yanıtlar:


111

Hatırladığım kadarıyla Drepper'ın içeriği bellekle ilgili temel kavramları anlatıyor: CPU önbelleğinin nasıl çalıştığı, fiziksel ve sanal bellek nedir ve Linux çekirdeği bu hayvanat bahçesini nasıl ele alıyor. Muhtemelen bazı örneklerde eski API referansları vardır, ancak önemli değildir; temel kavramların alaka düzeyini etkilemez.

Bu nedenle, temel bir şeyi tanımlayan herhangi bir kitap veya makale modası geçmiş olarak adlandırılamaz. "Her programcının bellek hakkında bilmesi gerekenler" kesinlikle okunmaya değer, ama, bunun "her programcı" için olduğunu sanmıyorum. Sistem / gömülü / çekirdek çocuklar için daha uygundur.


3
Evet, bir programcının neden SRAM ve DRAM'ın analog düzeyde nasıl çalıştığını bilmesi gerektiğini gerçekten anlamıyorum - bu, programlar yazarken çok yardımcı olmaz. Ve bu bilgiye gerçekten ihtiyaç duyan insanlar, gerçek zamanlamalar, vb. Hakkında kılavuzları okumak için daha iyi zaman harcarlar. Ama HW düşük seviyeli şeylerle ilgilenen insanlar için? Belki yararlı değil, ama en azından eğlenceli.
Voo

47
Günümüzde performans == bellek performansı, bu nedenle anlayış hafızadır herhangi yüksek performanslı uygulamasında en önemli şey. Bu, kâğıda dahil olan herkes için vazgeçilmezdir: oyun geliştirme, bilimsel bilgi işlem, finans, veritabanları, derleyiciler, büyük veri kümelerinin işlenmesi, görselleştirme, çok sayıda isteği yerine getirmek zorunda olan her şey ... Yani evet, bir uygulamada çalışıyorsanız Bu çoğu zaman boştur, bir metin editörü gibi, bir kelime bulmak, kelimeleri saymak, yazım denetimi ... oh bekle ... boşver gibi hızlı bir şeyler yapmanız gerekene kadar kağıt tamamen ilgisizdir.
gnzlbg

144

PDF biçimindeki rehber https://www.akkadia.org/drepper/cpumemory.pdf adresindedir .

Yine de genellikle mükemmel ve şiddetle tavsiye edilir (benim tarafımdan ve diğer performans ayarlama uzmanları tarafından düşünüyorum). Ulrich (veya başka biri) 2017 güncellemesi yazmış olsaydı harika olurdu, ancak bu çok fazla iş olurdu (örneğin, karşılaştırmaları yeniden çalıştırmak). Ayrıca, diğer x86 performans ayarlama ve SSE / asm (ve C / C ++) optimizasyon bağlantılarına bakın. etiket wiki . (Ulrich'in makalesi x86'ya özgü değildir, ancak kriterlerinin çoğu (tümü) x86 donanımındadır.)

DRAM ve önbelleklerin nasıl çalıştığıyla ilgili düşük düzeyli donanım ayrıntıları hala geçerlidir . DDR4, DDR1 / DDR2 (okuma / yazma patlaması) için açıklanan komutların aynısını kullanır . DDR3 / 4 iyileştirmeleri temel değişiklikler değildir. AFAIK, kemerden bağımsız tüm öğeler genel olarak hala geçerlidir, örneğin AArch64 / ARM32.

Ayrıca bkz Bu cevap Gecikme Bound Platformlar bölümü tek dişli bant genişliği bellek / L3 gecikme etkisi hakkında önemli detaylar için: ve bu aslında bir Xeon gibi modern bir sayıda çekirdekli CPU üzerinde tek dişli bant genişliği için birincil darboğaz olduğunu . Ancak dört çekirdekli Skylake masaüstü, DRAM bant genişliğini tek bir iş parçacığıyla en üst düzeye çıkarmaya yaklaşabilir. Bu bağlantı, x86'daki NT mağazaları ve normal mağazalar hakkında çok iyi bilgiler içeriyor. Skylake neden tek iş parçacıklı bellek çıktısı için Broadwell-E'den daha iyi? bir özettir.bandwidth <= max_concurrency / latency

Bu nedenle Ulrich'in 6.5.8'deki öneriyi , diğer NUMA düğümlerinde ve kendinizde uzak bellek kullanma hakkındaki tüm Bant Genişliğini Kullanmak , bellek denetleyicilerinin tek bir çekirdeğin kullanabileceğinden daha fazla bant genişliğine sahip olduğu modern donanımlarda karşı üretken. Büyük olasılıkla, düşük gecikmeli iş parçacıkları arası iletişim için aynı NUMA düğümünde birden çok belleğe aç iş parçacığının çalıştırılmasının net bir yararı olduğu, ancak yüksek bant genişliğine ve gecikmeye duyarlı olmayan şeyler için uzaktan bellek kullanmasının bir avantajı olduğunu hayal edebilirsiniz. Ancak bu oldukça belirsizdir, normalde konuları NUMA düğümleri arasında bölün ve yerel belleği kullanmasını sağlayın. Çekirdek başına bant genişliği maksimum eşzamanlılık sınırları nedeniyle gecikmeye duyarlıdır (aşağıya bakın), ancak bir soketteki tüm çekirdekler genellikle bu soketteki bellek denetleyicilerinden daha fazlasını doyurabilir.


(genellikle) Yazılım önceden getirme özelliğini kullanma

Değişen önemli bir şey, donanım ön getirmesinin Pentium 4'tekinden çok daha iyi olması ve oldukça büyük bir adımda ve aynı anda birden fazla akışı (örneğin, 4k sayfa başına bir ileri / geri) kadar uzun adımlara erişebilmesidir. Intel'in optimizasyon kılavuzu , Sandybridge ailesi mikro mimarileri için çeşitli önbellek düzeylerindeki HW ön getiricilerinin bazı ayrıntılarını açıklar. Ivybridge ve sonraki sürümlerde, hızlı başlatmayı tetiklemek için yeni sayfada önbellek kaybını beklemek yerine, bir sonraki sayfa donanımı önceden getirilir. AMD'nin optimizasyon kılavuzunda benzer şeyler olduğunu varsayalım. Intel'in el kitabının da, bazıları sadece P4 için iyi olan eski tavsiyelerle dolu olduğuna dikkat edin. Sandybridge'e özgü bölümler elbette SnB için doğrudur, ancak örn.mikro-kaynaşmış uopsların laminasyonunun kaldırılması HSW'de değişmiştir ve kılavuz bundan bahsetmemektedir .

Bu günlerde olağan tavsiye, tüm SW ön getirmesini eski koddan kaldırmaktır ve yalnızca profilleme önbellek hatalarını gösteriyorsa (ve bellek bant genişliğini doyurmuyorsanız) geri koymayı düşünebilirsiniz. Bir ikili aramanın bir sonraki adımının her iki tarafının önceden getirilmesi yine de yardımcı olabilir. örneğin, hangi elemanın bir sonraki bakacağına karar verdikten sonra, 1/4 ve 3/4 elemanlarını yükleme / kontrol ortası ile paralel olarak yüklenebilmeleri için önceden getirin.

Ayrı bir ön getirme iş parçacığı (6.3.4) kullanma önerisi tamamen eski , sanırım ve Pentium 4'te her zaman iyiydi. P4 hiper iş parçacığına (bir fiziksel çekirdeği paylaşan 2 mantıksal çekirdek) sahipti, ancak yeterli önbellek yoktu (ve / veya arızalı yürütme kaynakları) aynı çekirdek üzerinde iki tam hesaplama iş parçacığı çalıştıran verim elde etmek için. Ancak modern CPU'lar (Sandybridge-ailesi ve Ryzen) çok daha kasvetli ve gerçek bir iş parçacığı çalıştırmalı veya hiper iş parçacığı kullanmamalıdır (diğer mantıksal çekirdeği boşta bırakın, böylece solo iş parçacığı ROB'yi bölümlemek yerine tam kaynaklara sahip olur).

Yazılım önceden getirme her zaman "kırılgan" olmuştur : bir hızlanma elde etmek için doğru sihirli ayar numaraları, donanımın ayrıntılarına ve belki de sistem yüküne bağlıdır. Çok erken ve talep yükünden önce tahliye edildi. Çok geç ve yardımcı olmuyor. Bu blog makalesi , bir sorunun ardışık olmayan bölümünü önceden almak için Haswell'de SW ön getirme kullanma ilginç bir deney için kod + grafikler gösterir. Ayrıca bkz. Önceden alma talimatlarını düzgün bir şekilde kullanma? . NT ön getirme ilginçtir, ancak daha da kırılgandır çünkü L1'den erken tahliye, sadece L2'ye değil, L3 veya DRAM'a kadar gitmeniz gerektiği anlamına gelir. Her son performans düşüşüne ihtiyacınız varsa ve belirli bir makineyi ayarlayabiliyorsanız, SW ön getirme sıralı erişim için bakmaya değer, ancakolabilir Bellekte bottlenecking için yakın gelirken yapmak için yeterli ALU çalışma varsa yine bir yavaşlama olacak.


Önbellek satır boyutu hala 64 bayt. (L1D okuma / yazma bant genişliği çok yüksektir ve modern CPU'lar, L1D'ye isabet ederse saat başına 2 vektör yükü + 1 vektör mağazası yapabilir . Bkz. Önbellek nasıl bu kadar hızlı olabilir? ) AVX512 ile çizgi boyutu = vektör genişliği, böylece bir önbellek hattının tamamını tek bir talimatta yükleyebilir / depolayabilirsiniz. Bu nedenle, yanlış hizalanmış her yük / depo 256b AVX1 / AVX2 için birbirinin yerine önbellek sınırını aşar ve bu genellikle L1D'de olmayan bir dizi üzerinde döngüyü yavaşlatmaz.

Adres çalışma zamanında hizalanmışsa, hizalanmamış yük talimatları sıfır cezaya sahiptir, ancak derleyiciler (özellikle gcc), otomatik hizalama sırasında herhangi bir hizalama garantisi hakkında bilgi sahibi olurlarsa daha iyi kod yaparlar. Aslında hizasız ops genellikle hızlıdır, ancak sayfa bölünmeleri hala incinir (Skylake'de çok daha az; 100'e karşı sadece ~ 11 ekstra döngü gecikmesi, ancak yine de bir işlem cezası).


Ulrich'in tahmin ettiği gibi, her çok soketli sistem bugünlerde NUMA'dır: entegre bellek denetleyicileri standarttır, yani harici bir Kuzey Köprüsü yoktur. Ancak SMP artık çok soketli anlamına gelmez, çünkü çok çekirdekli CPU'lar yaygındır. Nehalem'den Skylake'e kadar olan Intel CPU'lar, çekirdekler arasındaki tutarlılık için geri dönüş noktası olarak geniş kapsamlı bir L3 önbellek kullandı . AMD işlemciler farklıdır, ancak ayrıntılar konusunda net değilim.

Skylake-X (AVX512) artık kapsamlı bir L3'e sahip değil, ancak hala tüm çekirdeklere snoops yayınlamadan çipte (ve eğer öyleyse) herhangi bir yerde önbelleğe alınanları kontrol etmesini sağlayan bir etiket dizini olduğunu düşünüyorum. SKX , maalesef önceki çok çekirdekli Xeons'tan daha kötü bir gecikme ile halka veriyolu yerine bir kafes kullanır .

Temel olarak, bellek yerleşimini optimize etme hakkındaki tüm tavsiyeler hala geçerlidir, sadece önbellek hatalarını veya çekişmeyi önleyemediğinizde ne olacağının ayrıntıları değişir.


6.4.2 Atomik operasyonlar : CAS-yeniden deneme döngüsünü donanım tarafından tahsis edilmiş olandan 4 kat daha kötü gösteren karşılaştırma lock addmuhtemelen maksimum çekişme durumunu yansıtmaktadır . Ancak gerçek çok iş parçacıklı programlarda, senkronizasyon minimumda tutulur (pahalı olduğu için), bu nedenle çekişme düşüktür ve CAS yeniden deneme döngüsü genellikle yeniden denemek zorunda kalmadan başarılı olur.

C ++ 11 std::atomic fetch_addbir lock add(veya lock xadddöndürme değeri kullanılıyorsa) derler , ancak locked komutuyla yapılamayan bir şey yapmak için CAS kullanan bir algoritma genellikle bir felaket değildir. Kullanım C ++ 11std::atomic veya C11 stdatomicyerine gcc mirası __syncyerleşik ins veya daha yeni __atomicankastre ins aynı konuma atomik olmayan atom erişimi karıştırmak istemiyorsanız ...

8.1 DWCAS ( cmpxchg16b) : Gcc'yi yaymak için koaksiyel yapabilirsiniz, ancak nesnenin sadece yarısının verimli yüklerini istiyorsanız, çirkin unionsaldırılara ihtiyacınız vardır : ABA sayacını c ++ 11 CAS ile nasıl uygulayabilirim? . (DWCAS'ı 2 ayrı bellek konumundaki DCAS ile karıştırmayın. DWAS ile kilitsiz atomik DC emülasyonu mümkün değildir, ancak işlemsel bellek (x86 TSX gibi) bunu mümkün kılar.)

8.2.4 işlem belleği : Birkaç yanlış başlatmadan sonra (nadiren tetiklenen bir hata nedeniyle bir mikro kod güncellemesi tarafından serbest bırakıldıktan sonra devre dışı bırakılır), Intel, geç model Broadwell ve tüm Skylake CPU'larında işlem belleğine sahiptir. Tasarım hala David Kanter'in Haswell için tanımladığı şeydir . Düzenli bir kilit kullanan (ve geri düşebilen) kodu hızlandırmak için bunu kullanmanın kilitli bir yolu vardır (özellikle bir kabın tüm öğeleri için tek bir kilitle), böylece aynı kritik bölümdeki birden çok iş parçacığı genellikle çarpışmaz ) veya doğrudan işlemler hakkında bilen bir kod yazmak için kullanabilirsiniz.


7.5 Hugepages : anonim şeffaf büyük sayfalar, hugetlbfs'yi manuel olarak kullanmak zorunda kalmadan Linux'ta iyi çalışır. 2MiB hizalaması ile> = 2MiB ayırma yapın (örn posix_memalign. Veyaaligned_alloc aptal ISO C ++ 17 gereksinimini başarısız olduğunda zorlamayan size % alignment != 0).

2MiB ile hizalanmış anonim bir ayırma varsayılan olarak devasa sayfaları kullanır. Bazı iş yükleri (örneğin, bunları yaptıktan sonra bir süre büyük ayırmalar kullanmaya devam eden)
echo always >/sys/kernel/mm/transparent_hugepage/defrag, çekirdeğin 4k sayfalara geri dönmek yerine gerektiğinde fiziksel belleği birleştirmesini sağlamaktan yararlanabilir. ( Çekirdek belgelerine bakın ). Alternatif olarak, madvise(MADV_HUGEPAGE)büyük tahsisler yaptıktan sonra kullanın (tercihen hala 2MiB hizalaması ile).


Ek B: Oprofile : Linux perfçoğunlukla yerini aldı oprofile. Belirli mikro yapılara özgü ayrıntılı olaylar için ocperf.pysargıyı kullanın . Örneğin

ocperf.py stat -etask-clock,context-switches,cpu-migrations,page-faults,cycles,\
branches,branch-misses,instructions,uops_issued.any,\
uops_executed.thread,idq_uops_not_delivered.core -r2 ./a.out

Bazı kullanım örnekleri için bkz. X86'nın MOV'u gerçekten "özgür" olabilir mi? Bunu neden hiç üretemiyorum? .


3
Çok öğretici cevap ve işaretçiler! Bu açıkça daha fazla oy hak ediyor!
claf

@Peter Kabloları Okumayı önerebileceğiniz başka kılavuzlar / makaleler var mı? Yüksek performanslı bir programcı değilim, ancak bu konuda daha fazla bilgi edinmek ve umarım günlük programıma dahil edebileceğim uygulamaları almak isterim.
user3927312

4
@ user3927312: agner.org/optimize , özellikle x86 için düşük düzeyli şeyler için en iyi ve en tutarlı rehberlerden biridir, ancak bazı genel fikirler diğer ISA'lar için geçerlidir. Asm kılavuzlarının yanı sıra, Agner'ın optimize edici bir C ++ PDF'si vardır. Diğer performans / CPU mimarisi bağlantıları için bkz. Stackoverflow.com/tags/x86/info . Ayrıca derleyicinin asm çıkışına bir göz atmaya değer olduğunda derleyici kritik döngüler için daha iyi bir asm yapmasına yardımcı olarak C ++ optimizasyonu hakkında yazdım: Collatz varsayımını elle yazılmış asmdan daha hızlı test etmek için C ++ kodu?
Peter Cordes

74

Hızlı bakışımdan oldukça doğru görünüyor. Dikkat edilmesi gereken tek şey, "tümleşik" ve "harici" bellek denetleyicileri arasındaki farktır. İ7 serisi Intel CPU'ların piyasaya sürülmesinden bu yana AMD, AMD64 yongalarının piyasaya sürülmesinden bu yana entegre bellek denetleyicileri kullanıyor.

Bu makale yazıldığından, bir çok şey değişmedi, hızlar arttı, bellek denetleyicileri çok daha akıllı hale geldi (i7, değişiklikleri taahhüt edene kadar RAM'e yazmayı geciktirecek), ancak bir çok şey değişmedi . En azından bir yazılım geliştiricisinin umursayacağı hiçbir şekilde değil.


5
İkinizi de kabul etmek isterdim. Ama yazınızı iptal ettim.
Framester

5
Muhtemelen SW geliştiricileri için en önemli değişiklik, önceden getirme iş parçacıklarının kötü bir fikir olmasıdır. CPU'lar hiper iş parçacıklı 2 tam iş parçacığı çalıştıracak kadar güçlüdür ve çok daha iyi HW önceden getirme özelliğine sahiptir. SW prefetch genel olarak , özellikle sıralı erişim için çok daha az önemlidir. Cevabımı gör.
Peter Cordes
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.