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.
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.
Yanıtlar:
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.
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.x86 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.
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 add
muhtemelen 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_add
bir lock add
(veya lock xadd
döndürme değeri kullanılıyorsa) derler , ancak lock
ed komutuyla yapılamayan bir şey yapmak için CAS kullanan bir algoritma genellikle bir felaket değildir. Kullanım C ++ 11std::atomic
veya C11 stdatomic
yerine gcc mirası __sync
yerleşik ins veya daha yeni __atomic
ankastre 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 union
saldı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.py
sargı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? .
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.