Önemli arka plan okuması: Agner Fog'un mikroarş pdf'si ve muhtemelen Ulrich Drepper'ın Her Programcının Bellek Hakkında Bilmesi Gerekenler . Ayrıca bkz.x86wiki'yi, özellikle Intel'in optimizasyon kılavuzlarını ve David Kanter'in Haswell mikro mimarisini analizini diyagramlarla birlikte .
Çok havalı bir görev; gerçek kodda önemli olmayan bir grup hileyi öğrenmek için öğrencilerin bazı kodları optimize etmeleri istendiğindegcc -O0
gördüklerimden çok daha iyi . Bu durumda, CPU boru hattı hakkında bilgi edinmeniz ve bunu sadece kör tahminde değil optimizasyon çabalarınıza rehberlik etmek için kullanmanız istenir. Bunun en eğlenceli yanı kasıtlı kötülük değil, her kötümserliği "şeytani yetersizlik" ile haklı çıkarmaktır.
Ödev ifadeleri ve kodla ilgili sorunlar :
Bu kod için uarch'a özgü seçenekler sınırlıdır. Herhangi bir dizi kullanmaz ve maliyetin büyük bir kısmı exp
/ log
kütüphane işlevlerine çağırılır. Aşağı yukarı talimat düzeyinde paralelliğe sahip olmanın bariz bir yolu yoktur ve döngüde taşınan bağımlılık zinciri çok kısadır.
Bağımlılıkları değiştirmek, ILP'yi sadece bağımlılıklardan (tehlikeler) azaltmak için ifadeleri yeniden düzenlemekten yavaşlamaya çalışan bir cevap görmek isterim . Ben denemedim.
Intel Sandybridge ailesi CPU'lar, çok sayıda transistör ve paralellik bulmak için güç harcayan ve klasik bir RISC sıralı boru hattını zorlaştıracak tehlikelerden (bağımlılıklar) kaçınan agresif sıra dışı tasarımlardır . Genellikle yavaşlatan geleneksel tehlikeler, iş akışının gecikmeyle sınırlanmasına neden olan RAW "gerçek" bağımlılıklardır.
Kayıtların yeniden adlandırılması sayesinde kayıtlar için SAVAŞ ve WAW tehlikeleri hemen hemen hiç sorun değil . (yalnızca yazılsa bile hedeflerine Intel CPU'larda yanlış bağımlılığı olanpopcnt
/lzcnt
/hariç. yani WAW, RAW tehlikesi + yazma olarak işlenir). Bellek siparişi için, modern CPU'lar emekli oluncaya kadar önbellek taahhüdünü geciktirmek için mağaza kuyruklarını kullanır ve ayrıca WAR ve WAW tehlikelerini önler .tzcnt
Mulss neden Haswell'de Agner'ın talimat tablolarından farklı olarak sadece 3 döngü alıyor? FP nokta ürün döngüsünde FMA gecikmesini kayıt yeniden adlandırma ve gizleme hakkında daha fazla bilgiye sahiptir.
"İ7" markası Nehalem (Core2'nin halefi) ile tanıtıldı ve bazı Intel kılavuzları Nehalem anlamına geldiklerinde "Core i7" bile diyorlar, ancak Sandybridge ve daha sonraki mikro mimariler için "i7" markasını korudular . SnB, P6 ailesinin yeni bir türe, SnB ailesine dönüştüğü zamandır . Nehalem, Pentium III ile Sandybridge'den daha fazla ortak noktaya sahiptir (örn. Kayıt tezgahları ve ROB-okuma tezgahları SnB'de gerçekleşmez, çünkü fiziksel bir kayıt dosyası olarak değiştirilmiştir. Ayrıca bir uop önbellek ve farklı bir dahili uop biçimi). "İ7 mimarisi" terimi yararlı değil, SnB ailesini Nehalem ile gruplamak çok mantıklı değil, Core2 ile değil. (Nehalem, birçok çekirdeği birbirine bağlamak için paylaşılan kapsayıcı L3 önbellek mimarisini tanıttı. Ayrıca GPU'ları da entegre etti. Çip düzeyinde, adlandırma daha mantıklı.)
Şeytani yetersizliğin haklı çıkarabileceği iyi fikirlerin özeti
Diyabolik açıdan yetersiz olanların bile açıkça işe yaramaz bir çalışma veya sonsuz bir döngü eklemesi olası değildir ve C ++ / Boost sınıflarıyla karışıklık yapmak ödev kapsamının ötesindedir.
- Tek bir paylaşımlı
std::atomic<uint64_t>
döngü sayacına sahip çok parçacıklı , böylece doğru toplam yineleme sayısı gerçekleşir. Atomik uint64_t özellikle kötüdür -m32 -march=i586
. Bonus puanları için, yanlış hizalanmasını ve düzensiz bir bölünmeyle (4: 4 değil) bir sayfa sınırını geçmesini sağlayın.
- Diğer bazı atomik olmayan değişkenler için yanlış paylaşım -> bellek emri yanlış spekülasyon boru hattı ve ekstra önbellek özledikleri temizlenir.
-
FP değişkenlerinde kullanmak yerine , işaret bitini çevirmek için 0x80 ile yüksek baytı XOR yaparak mağaza yönlendirme duraklarına neden olur .
- Her yinelemeyi bağımsız olarak, daha da ağır bir şeyle zamanlayın
RDTSC
. örneğin CPUID
/ RDTSC
veya bir sistem çağrı yapan bir zaman fonksiyonu. Serileştirme talimatları doğal olarak boru hattı dostu değildir.
- Değişiklik, sabitleri, karşılıklılarına bölmek için çarpar ("okuma kolaylığı için"). div yavaştır ve tamamen ardışık değildir.
- AVX (SIMD) ile çarpma / sqrt değerini vektörleyin, ancak
vzeroupper
skaler matematik kitaplığı exp()
ve log()
işlevlerine çağrı yapmadan önce kullanmayın , bu da AVX <-> SSE geçiş duraklarına neden olur .
- RNG çıkışını bağlantılı bir listede veya arızalı olarak geçtiğiniz dizilerde saklayın. Her yinelemenin sonucu için aynıdır ve sonunda toplanır.
Ayrıca bu cevapta da yer alan ancak özetin dışında bırakılan: Pipellenmemiş bir CPU'da olduğu kadar yavaş olacak veya şeytani yetersizlikle bile haklı görünmeyen öneriler. örneğin, açıkça farklı / daha kötü bir asm üreten birçok gimp-the-derleyici fikri.
Çok iş parçacığı kötü
Belki çok az yinelemeli çok iş parçacıklı döngüler için hız kazancından çok daha fazla ek yük ile OpenMP kullanın. Monte-carlo kodunuz, aslında bir hızlanma elde etmek için yeterli paralelliğe sahiptir. her yinelemeyi yavaşlatmayı başarabilirsek. (Her bir iş parçacığı payoff_sum
, sonuna eklenen bir kısmi hesaplar ). #omp parallel
bu döngüde muhtemelen bir optimizasyon değil, bir optimizasyon olurdu.
Çok-iş parçacığı ancak her iki iş parçacığını aynı döngü sayacını paylaşmaya zorlar ( atomic
toplam yineleme sayısının doğru olması için artışlarla). Bu şeytani mantıklı görünüyor. Bu, bir static
değişkeni döngü sayacı olarak kullanmak anlamına gelir . Bu atomic
, döngü sayaçlarının kullanımını haklı çıkarır ve gerçek önbellek hattı ping-pong oluşturma oluşturur (iş parçacıkları hiper iş parçacığıyla aynı fiziksel çekirdek üzerinde çalışmadığı sürece; bu kadar yavaş olmayabilir ). Her neyse, bu tartışmasız durumdan çok daha yavaş lock inc
. Ve 32 bitlik bir sistem üzerinde tartışılan bir lock cmpxchg8b
atomik artışı artırmak uint64_t
için donanım bir atomik tahkim etmek yerine bir döngü içinde yeniden denemek zorunda kalacak inc
.
Ayrıca , birden çok iş parçacığının özel verilerini (ör. RNG durumu) aynı önbellek satırının farklı baytlarında tuttuğu yanlış paylaşım oluşturun . (Bakılacak mükemmel sayaçlar dahil Intel hakkında öğretici) . Bunun mikro-mimariye özgü bir yönü var : Intel CPU'lar bellek yanlış sıralaması yapılmadığı konusunda spekülasyon yapıyor ve bunu tespit etmek için, en azından P4'te , bir bellek düzeni makine temizliği perf olayı var . Haswell'de ceza çok büyük olmayabilir. Bu bağın işaret ettiği gibi, lock
ed talimatı bunun olacağını varsayarak yanlış spekülasyonları önler. Normal bir yük, diğer çekirdeklerin, yükün yürütülmesi ile program sırasında emekli olması arasında bir önbellek satırını geçersiz kılmayacağını tahmin eder (kullanmadığınız sürecepause
). lock
Ed talimatları olmadan gerçek paylaşım genellikle bir hatadır. Atomik olmayan bir paylaşımlı döngü sayacını atomik durumla karşılaştırmak ilginç olacaktır. Gerçekten pesimize etmek için, paylaşılan atomik döngü sayacını koruyun ve başka bir değişken için aynı veya farklı bir önbellek satırında yanlış paylaşıma neden olun.
Rastgele aramaya özgü fikirler:
Eğer tanıtmak Eğer herhangi öngörülemeyen dalları , o ölçüde kodu pessimize edecektir. Modern x86 CPU'ların oldukça uzun boru hatları vardır, bu yüzden yanlış bir tahmin ~ 15 döngü (uop önbellekten çalıştırılırken) maliyeti.
Bağımlılık zincirleri:
Bence bu, görevin amaçlanan bölümlerinden biriydi.
Birden çok kısa bağımlılık zinciri yerine bir uzun bağımlılık zinciri olan bir işlem sırası seçerek CPU'nun komut düzeyi paralelliğinden yararlanma yeteneğini yenin. Siz kullanmadıkça derleyicilerin FP hesaplamaları için işlem sırasını değiştirmesine izin verilmez -ffast-math
, çünkü bu sonuçları değiştirebilir (aşağıda tartışıldığı gibi).
Bunu gerçekten etkili hale getirmek için, döngü ile taşınan bir bağımlılık zincirinin uzunluğunu arttırın. Yine de hiçbir şey apaçık ortada değildir: Yazıldığı gibi ilmeklerin çok kısa döngü-taşınan bağımlılık zincirleri vardır: sadece bir FP ekleme. (3 döngü). Birden fazla yineleme hesaplamaları aynı anda uçuşta olabilir, çünkü payoff_sum +=
önceki yinelemenin sonundan çok önce başlayabilirler . ( log()
ve exp
birçok talimat alın, ancak Haswell'in paralellik bulmak için sıra dışı penceresinden çok daha fazla değil : ROB boyutu = 192 kaynaşmış alan adı ve zamanlayıcı boyutu = 60 kullanılmayan alan adı. Mevcut yinelemenin yürütülmesi, bir sonraki yinelemeden gelen talimatlar için talimatlar için yeterince yer açtığında, girişleri hazır olan herhangi bir kısmı (yani bağımsız / ayrı dep zinciri) daha eski talimatlar yürütme birimlerinden ayrıldığında çalışmaya başlayabilir ücretsiz (örneğin, iş hacminde değil, gecikmede tıkanmış oldukları için).
RNG durumu neredeyse kesinlikle döngüden taşınan bir bağımlılık zinciri olacaktır addps
.
Daha yavaş / daha fazla FP işlemi kullanın (özellikle daha fazla bölüm):
0,5 ile çarpmak yerine 2,0'a bölün ve bu şekilde devam edin. FP multiply, Intel tasarımlarında yoğun bir şekilde boru hatlarına sahiptir ve Haswell ve sonraki sürümlerde 0,5c'lik bir verime sahiptir. FP divsd
/ divpd
sadece kısmen boru hatlıdır . ( divpd xmm
Skylake, 13-14c gecikme süresi için 4c başına etkileyici bir verime sahip olmasına rağmen , Nehalem'de (7-22c) hiç boru hattına girmemiş).
do { ...; euclid_sq = x*x + y*y; } while (euclid_sq >= 1.0);
Açıkça bu kadar açıkça uygun olacağını, bir mesafe için test ediyor sqrt()
ona. : P ( sqrt
daha da yavaştır div
).
@Paul Clayton'un önerdiği gibi, ilişkilendirilebilir / dağıtıcı eşdeğerleriyle ifadelerin yeniden yazılması daha fazla iş getirebilir ( -ffast-math
derleyicinin yeniden optimize edilmesine izin vermek için kullanmadığınız sürece ). (exp(T*(r-0.5*v*v))
olabilir exp(T*r - T*v*v/2.0)
. Reel sayılar üzerinde matematik ilişkisel durumdayken bu Not nokta matematik yüzen olduğunu değil hatta taşması dikkate almadan, / NaN (neden -ffast-math
varsayılan olarak değil). Çok kıllı iç içe bir öneri için Paul'un yorumuna bakın pow()
.
Hesaplamaları çok küçük sayılara ölçekleyebiliyorsanız, iki normal sayıdaki bir işlem denormal ürettiğinde FP math ops mikrokodun tuzağına düşmek için ~ 120 ekstra döngü alır . Kesin sayılar ve ayrıntılar için Agner Sis'in mikroarş pdf'sine bakın. Çok fazla çarpanınız olduğu için bu olası değildir, bu nedenle ölçek faktörü kare olur ve 0.0'a kadar taşar. Gerekli ölçeklendirmeyi yetersizlikle (hatta şeytani) haklı çıkarmanın hiçbir yolunu görmüyorum, sadece kasıtlı kötülük.
Eğer intrinsics ( <immintrin.h>
) kullanabilirsiniz
movnti
Verilerinizi önbellekten çıkarmak için kullanın . Şeytani: Yeni ve zayıf bir şekilde sipariş edildi, bu yüzden CPU'nun daha hızlı çalışmasına izin vermeli, değil mi? Ya da birisinin tam olarak bunu yapma tehlikesi olan bir vaka için bu bağlantılı soruya bakın (sadece bazı yerlerin sıcak olduğu dağınık yazılar için). clflush
kötü niyetli olmadan muhtemelen imkansızdır.
Bypass gecikmelerine neden olmak için FP matematik işlemleri arasında tamsayı karıştırmaları kullanın.
Kullanımı olmaksızın SSE ve AVX talimatları Karıştırma vzeroupper
öncesi Skylake sebeplerini büyük tezgahları (ve farklı bir ceza Skylake içinde ). Onsuz bile, kötü bir şekilde vektörleme skalerden daha kötü olabilir (256b vektör ile bir kerede 4 Monte-Carlo yinelemesi için add / sub / mul / div / sqrt işlemlerini yaparak kaydedilenlerden daha fazla veriyi vektörlere / dışına harcayarak harcamak daha fazla döngü) . add / sub / mul yürütme birimleri tamamen ardışık düzenlidir ve tam genişliktedir, ancak 256b vektörlerdeki div ve sqrt, 128b vektörlerindeki (veya skaler) kadar hızlı değildir, bu nedenle hızlanma dramatik değildirdouble
.
exp()
ve log()
donanım desteğine sahip olmadığından, parça vektör öğelerini skalere geri çekmeyi ve kitaplık işlevini ayrı olarak çağırmayı, ardından sonuçları bir vektör haline getirmeyi gerektirir. libm genellikle yalnızca SSE2 kullanmak üzere derlenir, bu nedenle skaler matematik komutlarının eski SSE kodlarını kullanır. Kodunuz birincisi exp
olmadan 256b vektör ve çağrı kullanıyorsa, durursunuz vzeroupper
. Döndükten sonra, bir AVX-128 talimatı bir vmovsd
sonraki vektör öğesini bir argüman olarak ayarlamayı sever exp
. Ve sonra exp()
bir SSE talimatı çalıştırdığında tekrar durur. Bu soruda tam olarak bu oldu ve 10x yavaşlamaya neden oldu . (Teşekkürler @ZBoson).
Ayrıca bu kod için Nathan Kurz'un Intel matematik lib vs. glibc ile yaptığı deneylere de bakın . Gelecek glibc, vektörize edilmiş uygulamalar exp()
ve benzeri ile gelecek.
IvB öncesi veya esp. Nehalem, 16 bit veya 8 bit işlemlerle 32 bit veya 64 bit işlemlerle kısmi kayıt tezgahlarına neden olmak için gcc almaya çalışın. Çoğu durumda, gcc movzx
8 veya 16 bitlik bir işlemden sonra kullanacaktır , ancak gcc'nin değiştiği ah
ve ardından okuduğu bir durumax
(Satır içi) asm ile:
(Satır içi) asm ile uop önbelleğini kırabilirsiniz: Üç 6uop önbellek satırına sığmayan 32B kod parçası, uop önbellekten kod çözücülere geçiş yapmaya zorlar. İç döngünün içindeki bir dal hedefinde birkaç uzun ALIGN
bayt nop
yerine çok sayıda tek bayt kullanan bir yetersizlik nop
hile yapabilir. Veya hizalama dolgusunu etiket yerine etiket yerine yerleştirin. : P Bu sadece ön uç bir darboğazsa önemlidir, bu da kodun geri kalanını kötüleştirmeyi başaramazsak olmaz.
Boru hattı temizlemelerini tetiklemek için kendi kendini değiştiren kod kullanın (makine-nükleer).
8 bit sığmayacak kadar büyük olan 16 bitlik talimatlardan LCP tezgahlarının yararlı olması pek olası değildir. SnB ve sonrasındaki uop önbellek kod çözme cezasını yalnızca bir kez ödediğiniz anlamına gelir. Nehalem'de (ilk i7), 28 uop döngü arabelleğine sığmayan bir döngü için çalışabilir. gcc bazen -mtune=intel
32bit talimatı kullanmış olsa bile bu talimatları yapacaktır .
Zamanlama için ortak bir deyim CPUID
(serileştirmek)RDTSC
. Her bir yinelemeyi bir CPUID
/ ile ayrı ayrı zamanlayarak, daha önceki talimatlarla yeniden sıralanmadığından RDTSC
emin olun RDTSC
, bu da işleri çok yavaşlatır . (Gerçek hayatta, zamanlamanın akıllı yolu, her birini ayrı ayrı zamanlamak ve eklemek yerine tüm iterasyonları birlikte zamanlamaktır).
Çok fazla önbellek kaybına ve diğer bellek yavaşlamalarına neden olun
union { double d; char a[8]; }
Bazı değişkenleriniz için a kullanın . Baytlardan yalnızca birine dar bir mağaza (veya Okuma-Değiştirme-Yazma) yaparak mağaza yönlendirme duraklarına neden olur. (Bu wiki makalesi ayrıca yük / mağaza kuyrukları için diğer birçok mikro mimari öğeyi de kapsar) örneğin XOR 0x80 kullanan bir işaretidouble
bir -
operatör yerine yalnızca yüksek bayt üzerinde çevirin . Diyabolik açıdan beceriksiz geliştirici, FP'nin tamsayıdan daha yavaş olduğunu duymuş olabilir ve bu nedenle tamsayı op'larını kullanarak mümkün olduğunca yapmaya çalışabilir. (SSE kayıtlarında FP matematiğini hedefleyen çok iyi bir derleyici bunu muhtemelen birxorps
başka bir xmm kaydında sabit ile, ancak bunun x87 için korkunç olmaması için tek yol, derleyicinin değeri reddettiğinin farkına varması ve bir sonraki eklentiyi çıkarma ile değiştirmesidir.)
Kullanım volatile
sizinle derleme yapıyorsanız -O3
kullanarak değil, std::atomic
aslında mağazaya derleyici zorlamak için, / biryere yükleyin. Genel değişkenler (yerel ayarlar yerine) bazı depoları / yeniden yüklemeleri de zorlar, ancak C ++ bellek modelinin zayıf sıralaması derleyicinin her zaman belleğe dökülmesini / yeniden yüklenmesini gerektirmez.
Yerel değişkenleri büyük bir yapının üyeleriyle değiştirin, böylece bellek düzenini kontrol edebilirsiniz.
Dolgu için yapıdaki dizileri kullanın (ve varlıklarını haklı çıkarmak için rastgele sayılar depolayın).
Her şeyin L1 önbelleğinde aynı "kümede" farklı bir satıra girmesi için bellek düzeninizi seçin . Sadece 8-yönlü çağrışımdır, yani her kümenin 8 "yolu" vardır. Önbellek çizgileri 64B'dir.
Daha da iyisi, şeyleri tam olarak 4096B ayırın, çünkü yükler farklı sayfalara depolara yanlış bir bağımlılığa sahiptir, ancak bir sayfadaki aynı ofsetle . Agresif sıra dışı CPU'lar , yüklerin ve depoların sonuçları değiştirmeden ne zaman yeniden sıralanabileceğini anlamak için Bellek Dezavantajını kullanır ve Intel'in uygulamasında yüklerin erken başlamasını engelleyen yanlış pozitifler bulunur. Muhtemelen sadece sayfa ofsetinin altındaki bitleri kontrol ederler, böylece TLB yüksek bitleri sanal bir sayfadan fiziksel bir sayfaya çevirmeden önce kontrol başlayabilir. Agner'ın kılavuzunun yanı sıra Stephen Canon'un yanıtı ve aynı zamanda @Krazy Glew'in aynı soruya vereceği cevabın sonuna yakın bir bölüme bakın. (Andy Glew, Intel'in orijinal P6 mikro mimarisinin mimarlarından biriydi.)
__attribute__((packed))
Değişkenleri önbellek satırını ve hatta sayfa sınırlarını kapsayacak şekilde yanlış hizalamanıza izin vermek için kullanın . (Yani bir yük double
iki önbellek satırından veri gerekir). Yanlış hizalanmış yüklerin, önbellek satırlarını ve sayfa satırlarını geçme durumu dışında hiçbir Intel i7 uarch'da herhangi bir cezası yoktur. Önbellek hattı bölünmeleri yine de fazladan döngü gerektirir . Skylake, sayfa bölünmüş yüklerin cezasını 100'den 5 döngüye kadar önemli ölçüde azaltır . (Bölüm 2.1.3) . Belki de iki sayfa yürüyüşü paralel olarak yapabilmekle ilgilidir.
A sayfasındaki bir sayfa bölmesi atomic<uint64_t>
en kötü durumda olmalıdır , özellikle. bir sayfada 5 bayt ve diğer sayfada 3 bayt veya 4: 4 dışında bir şey varsa. Ortadan aşağıya bölünmeler bile, IIRC gibi bazı uarch'larda 16B vektörleri olan önbellek çizgileri için daha etkilidir. alignas(4096) struct __attribute((packed))
RNG sonuçları için depolama dizisi de dahil olmak üzere her şeyi (elbette yerden tasarruf etmek için) koyun . uint8_t
Veya ile yanlış hizalama elde edin.uint16_t
önce şey .
Derleyicinin dizinli adresleme modlarını kullanmasını sağlayabilirseniz, bu mikro-füzyonu bozar . Belki #define
basit skaler değişkenleri değiştirmek için smy_data[constant]
.
Fazladan bir dolaylı yükleme düzeyi ekleyebiliyorsanız, yükleme / depolama adresleri erken bilinmemektedir, bu da daha da kötüleşebilir.
Dizileri bitişik olmayan sırada çaprazlama
Sanırım ilk etapta bir dizi tanıtmak için yetersiz bir gerekçe bulabiliriz: Rasgele sayı üretimini rastgele sayı kullanımından ayırmamızı sağlar. Her yinelemenin sonuçları daha sonra toplanacak bir dizide de saklanabilir (daha şeytani yetersizlikle).
"Maksimum rasgelelik" için, rasgele dizi üzerinde yeni rasgele sayılar yazan bir iş parçacığına sahip olabiliriz. Rasgele sayıları tüketen iş parçacığı, rasgele bir sayı yüklemek için rasgele bir dizin oluşturabilir. (Burada bazı işler var, ancak mikro mimari olarak yük adreslerinin erken bilinmesine yardımcı olur, böylece yüklenen verilere ihtiyaç duyulmadan önce olası herhangi bir yük gecikmesi çözülebilir.) Farklı çekirdeklerde bir okuyucu ve yazar olması, bellek sıralamasında yanlışlığa neden olur. - spekülasyon boru hattı temizlenir (yanlış paylaşım vakası için daha önce tartışıldığı gibi).
Maksimum kötüleşme için, dizinizin üzerinde 4096 baytlık bir adımla (yani 512 çift) döngü yapın. Örneğin
for (int i=0 ; i<512; i++)
for (int j=i ; j<UPPER_BOUND ; j+=512)
monte_carlo_step(rng_array[j]);
Yani erişim düzeni 0, 4096, 8192, ...,
8, 4104, 8200, ...
16, 4112, 8208, ...
Bunun gibi bir 2D diziye erişmek için elde edeceğiniz şey budur double rng_array[MAX_ROWS][512]
yanlış sırayla (@JesperJuhl tarafından önerildiği gibi, iç döngüdeki bir satırdaki sütunlar yerine satırlar üzerinde döngü yapmak). Şeytani yetersizlik bir 2D diziyi böyle boyutlarla haklı gösterebilirse, bahçe çeşitliliği gerçek dünyadaki yetersizlik, döngüyü yanlış erişim modeliyle kolayca haklı çıkarır. Bu gerçek hayatta gerçek kodda olur.
Dizi o kadar büyük değilse, aynı birkaç sayfayı yeniden kullanmak yerine birçok farklı sayfa kullanmak için döngü sınırlarını ayarlayın. Donanım önceden getirme sayfalar arasında (hiç / hiç) çalışmaz. Prefetcher, her sayfada bir ileri ve bir geri akışı izleyebilir (burada olan şey budur), ancak yalnızca bellek bant genişliği önceden getirme ile doymamışsa buna göre hareket eder.
Bu, sayfalar büyük bir sayfaya birleştirilmediği sürece ( Linux bunu fırsatlı olarak / dosya kullanımı olmayan) için anonim (dosya destekli olmayan) ayırmalar için yapmazsa, birçok TLB özlüyor oluşturur.malloc
new
mmap(MAP_ANONYMOUS)
).
Sonuç listesini saklamak için bir dizi yerine, bağlantılı bir liste kullanabilirsiniz . Daha sonra her yineleme bir işaretçi yükü gerektirir (bir sonraki yükün yük adresi için RAW gerçek bağımlılık tehlikesi). Kötü bir ayırıcı ile, önbellek yenerek, bellekte liste düğümleri dağıtabilirsiniz. Şeytani olarak yetersiz bir ayırıcı ile, her düğümü kendi sayfasının başına koyabilir. (örneğin mmap(MAP_ANONYMOUS)
, doğru şekilde desteklemek için sayfaları bölmeden veya nesne boyutlarını izlemeden doğrudan tahsis edin free
).
Bunlar gerçekten mikro mimariye özgü değildir ve boru hattıyla çok az ilgisi vardır (bunların çoğu aynı zamanda pipeline edilmemiş bir CPU'da bir yavaşlama olacaktır).
Biraz konu dışı: derleyicinin daha kötü kod üretmesini sağlayın / daha fazla iş yapın:
Kullanımı C ++ 11 std::atomic<int>
ve std::atomic<double>
en çok pessimal kodu. MFENCE'lar ve lock
ed talimatları, başka bir mesaj dizisinden çekişmeden bile oldukça yavaştır.
-m32
x87 kodu SSE2 kodundan daha kötü olacağından kod daha yavaş olacaktır. Yığın tabanlı 32bit çağrı kuralı daha fazla talimat alır ve yığın üzerindeki FP değişkenlerini bile işlevlere geçirir exp()
. atomic<uint64_t>::operator++
on -m32
bir lock cmpxchg8B
döngü gerektirir (i586). (Öyleyse bunu döngü sayaçları için kullanın!
-march=i386
Ayrıca pesimize edecek (teşekkürler @Jesper). FP karşılaştırmaları fcom
686'dan yavaştır fcomi
. Pre-586 atomik bir 64bit mağaza sağlamaz, (bir cmpxchg bırakın), bu yüzden tüm 64bitatomic
ops libgcc fonksiyon çağrılarına derlenir (aslında bir kilit kullanmak yerine muhtemelen i686 için derlenir). Son paragraftaki Godbolt Derleyici Gezgini bağlantısında deneyin.
Sizeof ( ) öğesinin 10 veya 16 olduğu ABI'larda ( hizalama için dolgu ile) ekstra hassasiyet ve ekstra yavaşlık için long double
/ sqrtl
/ kullanın . (IIRC, 64-bit, Windows kullanımları 8byte eşdeğer . (Neyse, yükleme / 10byte haznesi (80bit) AP işlenen 4/7 UOPs, VS. veya sadece her bir UOP 1 alarak / ' ). İle X87 zorlamak yendi otomatik vektörleştirme için bile gcc .expl
long double
long double
double
float
double
fld m64/m32
fst
long double
-m64 -march=haswell -O3
Kullanılmıyorsa atomic<uint64_t>
döngü sayaçları kullanmak long double
döngü sayaçları dahil her şey.
atomic<double>
derler, ancak bunun için okuma-değiştirme-yazma işlemleri +=
desteklenmez (64 bit'de bile). atomic<long double>
sadece atomik yükler / depolar için kütüphane fonksiyonunu çağırmalıdır. Muhtemelen gerçekten verimsizdir, çünkü x86 ISA, atomik 10bayt yükleri / depoları doğal olarak desteklemez ve kilitleme ( cmpxchg16b
) olmadan düşünebilmemin tek yolu 64 bit modu gerektirir.
En -O0
fazla mağaza / reloads neden olacaktır geçici vars için parçaları atayarak büyük bir ifadeyi kesiliyor. Olmadan volatile
ya da bir şey, bu gerçek kod gerçek bir yapı kullanır optimizasyon ayarları ile önemli değildir.
C takma kuralları char
, a'nın herhangi bir şeyi takmalarına izin verir , bu nedenle char*
derleyiciyi bayt deposundan önce / sonra bile her şeyi depolamaya / yeniden yüklemeye zorlar -O3
. (Bu, örneğin bir dizide çalışan kodunuint8_t
otomatik vektörleştirilmesi için bir sorundur .)
uint16_t
Büyük olasılıkla 16bit işlenen boyutu (potansiyel tezgahlar) ve / veya ekstra movzx
talimatlar (güvenli) kullanarak kesmeyi 16bit'e zorlamak için döngü sayaçlarını deneyin . İmzalı taşma tanımsız bir davranıştır , bu nedenle -fwrapv
veya en azından kullanmadıkça -fno-strict-overflow
, imzalı döngü sayaçlarının 64 bit işaretçilere ofset olarak kullanılsa bile her yinelemeyi yeniden imzalaması gerekmez .
Tamsayıdan float
geriye ve tekrar dönüşüme zorla . Ve / veya double
<=> float
dönüşümler. Talimatlar birden fazla gecikmeye sahiptir ve skaler int-> float ( cvtsi2ss
), xmm kaydının geri kalanını sıfırlamayacak şekilde kötü bir şekilde tasarlanmıştır. (gcc pxor
, bu nedenle bağımlılıkları kesmek için fazladan bir değer ekler .)
CPU yakınlığınızı sık sık farklı bir CPU'ya ayarlayın (@Egwor tarafından önerilir). şeytani akıl yürütme: Bir çekirdeğin uzun süre ipliğinizi çalıştırmasını aşırı ısıtmak istemezsiniz, değil mi? Belki başka bir çekirdeğe takas etmek, bu çekirdek turboyu daha yüksek bir saat hızına getirecektir. (Gerçekte: termal olarak birbirine çok yakınlar, çok soketli bir sistem haricinde bu pek olası değildir). Şimdi ayarlamayı yanlış yapın ve çok sık yapın. İşletim sistemi kaydetme / geri yükleme iş parçacığı durumunda harcanan zamanın yanı sıra, yeni çekirdek soğuk L2 / L1 önbellekleri, uop önbellek ve dal tahmin edicilerine sahiptir.
Gereksiz sistem çağrılarının sık sık yapılması, ne olursa olsun sizi yavaşlatabilir. Bazı önemli ama basit olanlar gettimeofday
, çekirdek moduna geçiş olmadan, kullanıcı boşluğunda uygulanabilir. (Linux üzerinde glibc bunu çekirdeğin kodunu verdiği için çekirdek yardımı ile yapar vdso
).
Sistem çağrısı yükü hakkında daha fazla bilgi için (yalnızca içerik anahtarının kendisini değil, kullanıcı alanına döndükten sonra önbellek / TLB özledikleri dahil ), FlexSC kağıdının mevcut durum hakkında mükemmel bir karşı sayaç analizi ve toplu iş sistemi teklifi vardır çok iş parçacıklı sunucu işlemlerinden çağrılar.
while(true){}