Intel Sandybridge ailesi CPU'larda boru hattı için bir programın deoptimize edilmesi


322

Bir haftadır beynimi bu görevi tamamlamaya çalışıyorum ve burada birinin beni doğru yola götürmesini umuyorum. Öğretmenin talimatları ile başlayayım:

Ödeviniz, asal sayı programını optimize etmek için ilk laboratuvar ödevimizin tersidir. Bu ödevdeki amacınız programı kötüleştirmek, yani daha yavaş çalışmasını sağlamaktır. Bunların her ikisi de CPU-yoğun programlardır. Laboratuar bilgisayarlarımızda çalışması birkaç saniye sürüyor. Algoritmayı değiştiremezsiniz.

Programı deoptimize etmek için Intel i7 boru hattının nasıl çalıştığına dair bilginizi kullanın. SAVAŞ, HAM ve diğer tehlikeleri ortaya çıkarmak için talimat yollarını yeniden sıralamanın yollarını hayal edin. Önbelleğin etkinliğini en aza indirmenin yollarını düşünün. Şeytani olarak beceriksiz olun.

Görev, Whetstone veya Monte-Carlo programları arasından seçim yaptı. Önbellek etkinliği yorumları çoğunlukla Whetstone için geçerlidir, ancak Monte-Carlo simülasyon programını seçtim:

// Un-modified baseline for pessimization, as given in the assignment
#include <algorithm>    // Needed for the "max" function
#include <cmath>
#include <iostream>

// A simple implementation of the Box-Muller algorithm, used to generate
// gaussian random numbers - necessary for the Monte Carlo method below
// Note that C++11 actually provides std::normal_distribution<> in 
// the <random> library, which can be used instead of this function
double gaussian_box_muller() {
  double x = 0.0;
  double y = 0.0;
  double euclid_sq = 0.0;

  // Continue generating two uniform random variables
  // until the square of their "euclidean distance" 
  // is less than unity
  do {
    x = 2.0 * rand() / static_cast<double>(RAND_MAX)-1;
    y = 2.0 * rand() / static_cast<double>(RAND_MAX)-1;
    euclid_sq = x*x + y*y;
  } while (euclid_sq >= 1.0);

  return x*sqrt(-2*log(euclid_sq)/euclid_sq);
}

// Pricing a European vanilla call option with a Monte Carlo method
double monte_carlo_call_price(const int& num_sims, const double& S, const double& K, const double& r, const double& v, const double& T) {
  double S_adjust = S * exp(T*(r-0.5*v*v));
  double S_cur = 0.0;
  double payoff_sum = 0.0;

  for (int i=0; i<num_sims; i++) {
    double gauss_bm = gaussian_box_muller();
    S_cur = S_adjust * exp(sqrt(v*v*T)*gauss_bm);
    payoff_sum += std::max(S_cur - K, 0.0);
  }

  return (payoff_sum / static_cast<double>(num_sims)) * exp(-r*T);
}

// Pricing a European vanilla put option with a Monte Carlo method
double monte_carlo_put_price(const int& num_sims, const double& S, const double& K, const double& r, const double& v, const double& T) {
  double S_adjust = S * exp(T*(r-0.5*v*v));
  double S_cur = 0.0;
  double payoff_sum = 0.0;

  for (int i=0; i<num_sims; i++) {
    double gauss_bm = gaussian_box_muller();
    S_cur = S_adjust * exp(sqrt(v*v*T)*gauss_bm);
    payoff_sum += std::max(K - S_cur, 0.0);
  }

  return (payoff_sum / static_cast<double>(num_sims)) * exp(-r*T);
}

int main(int argc, char **argv) {
  // First we create the parameter list                                                                               
  int num_sims = 10000000;   // Number of simulated asset paths                                                       
  double S = 100.0;  // Option price                                                                                  
  double K = 100.0;  // Strike price                                                                                  
  double r = 0.05;   // Risk-free rate (5%)                                                                           
  double v = 0.2;    // Volatility of the underlying (20%)                                                            
  double T = 1.0;    // One year until expiry                                                                         

  // Then we calculate the call/put values via Monte Carlo                                                                          
  double call = monte_carlo_call_price(num_sims, S, K, r, v, T);
  double put = monte_carlo_put_price(num_sims, S, K, r, v, T);

  // Finally we output the parameters and prices                                                                      
  std::cout << "Number of Paths: " << num_sims << std::endl;
  std::cout << "Underlying:      " << S << std::endl;
  std::cout << "Strike:          " << K << std::endl;
  std::cout << "Risk-Free Rate:  " << r << std::endl;
  std::cout << "Volatility:      " << v << std::endl;
  std::cout << "Maturity:        " << T << std::endl;

  std::cout << "Call Price:      " << call << std::endl;
  std::cout << "Put Price:       " << put << std::endl;

  return 0;
}

Yaptığım değişiklikler, kod çalışma süresini bir saniye arttırmak gibiydi, ancak tamamen kod eklemeden boru hattını durdurmak için neyi değiştirebileceğimden emin değilim. Doğru yönde bir nokta harika olurdu, herhangi bir cevabı takdir ediyorum.


Güncelleme: Bu ödevi veren profesör bazı ayrıntılar yayınladı

Öne çıkan özellikler:

  • Bir devlet okulunda ikinci dönem mimarlık dersidir (Hennessy ve Patterson ders kitabını kullanarak).
  • laboratuvar bilgisayarlarında Haswell CPU'lar var
  • Öğrenciler CPUIDtalimatlara ve önbellek boyutunun yanı sıra içsel ve CLFLUSHtalimatın nasıl belirleneceğine maruz kalmıştır .
  • herhangi bir derleyici seçeneğine izin verilir ve böylece satır içi asm.
  • Kendi karekök algoritmanızı yazmanın solgunun dışında olduğu açıklandı

Cowmoogun'un meta iş parçacığı üzerindeki yorumları, derleyici optimizasyonlarının bunun bir parçası olabileceğini ve varsayıldığını-O0 ve çalışma zamanında% 17'lik bir artışın makul olduğunu gösteriyor.

Öyleyse, ödevin amacı, öğrencilerin talimat düzeyinde paralellik ya da bunun gibi şeyleri azaltmak için mevcut işi yeniden sipariş etmelerini sağlamak gibi görünüyor, ancak insanların daha derinlemesine araştırdığı ve daha fazla öğrendiği kötü bir şey değil.


Bunun bir bilgisayar mimarisi sorusu olduğunu, C ++ 'nın genel olarak nasıl yavaşlatılacağı hakkında bir soru olmadığını unutmayın.


97
I7'nin çok kötü işittiğini duydumwhile(true){}
Cliff AB

3
HN atm'de
mlvljr

5
Openmp ile kötü yaparsanız, N ipliklerinin 1'den uzun
Flekso

9
Bu soru şimdi görüşülmekte olan meta
Madara'nın Hayalet

3
@bluefeet: Ekledim, çünkü yeniden açıldıktan bir saatten kısa bir süre içinde bir yakın oyu almıştı. Metada tartışıldığını görmek için okuma yorumları gerçekleştirmeden sadece 5 kişi ve VTC gelmelidir. Şimdi yakın bir oy daha var. En az bir cümlenin yakın / yeniden açılma döngülerinden kaçınmaya yardımcı olacağını düşünüyorum.
Peter Cordes

Yanıtlar:


405

Ö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.wiki'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/ logkü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/ RDTSCveya 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 vzeroupperskaler 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 parallelbu 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 ( atomictoplam yineleme sayısının doğru olması için artışlarla). Bu şeytani mantıklı görünüyor. Bu, bir staticdeğ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 cmpxchg8batomik artışı artırmak uint64_tiç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, locked 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 ). lockEd 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 expbirç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/ divpdsadece kısmen boru hatlıdır . ( divpd xmmSkylake, 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 ( sqrtdaha 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-mathderleyicinin 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-mathvarsayı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

movntiVerilerinizi ö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). clflushkö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 expolmadan 256b vektör ve çağrı kullanıyorsa, durursunuz vzeroupper. Döndükten sonra, bir AVX-128 talimatı bir vmovsdsonraki 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 movzx8 veya 16 bitlik bir işlemden sonra kullanacaktır , ancak gcc'nin değiştiği ahve 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 ALIGNbayt nopyerine çok sayıda tek bayt kullanan bir yetersizlik nophile 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=intel32bit 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 RDTSCemin 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 volatilesizinle derleme yapıyorsanız -O3kullanarak değil, std::atomicaslı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 doubleiki ö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_tVeya ile yanlış hizalama elde edin.uint16_t önce şey .

Derleyicinin dizinli adresleme modlarını kullanmasını sağlayabilirseniz, bu mikro-füzyonu bozar . Belki #definebasit 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.mallocnewmmap(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 locked talimatları, başka bir mesaj dizisinden çekişmeden bile oldukça yavaştır.

-m32x87 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 -m32bir lock cmpxchg8Bdöngü gerektirir (i586). (Öyleyse bunu döngü sayaçları için kullanın!

-march=i386Ayrıca pesimize edecek (teşekkürler @Jesper). FP karşılaştırmaları fcom686'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 .expllong doublelong doubledoublefloatdoublefld m64/m32fstlong double-m64 -march=haswell -O3

Kullanılmıyorsa atomic<uint64_t>döngü sayaçları kullanmak long doubledö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 -O0fazla mağaza / reloads neden olacaktır geçici vars için parçaları atayarak büyük bir ifadeyi kesiliyor. Olmadan volatileya 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_tBüyük olasılıkla 16bit işlenen boyutu (potansiyel tezgahlar) ve / veya ekstra movzxtalimatlar (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 -fwrapvveya 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 floatgeriye ve tekrar dönüşüme zorla . Ve / veya double<=> floatdö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.


10
@JesperJuhl: evet, bu gerekçesi satın alacağım. "şeytani beceriksiz" harika bir ifade :)
Peter Cordes

2
Katları sabitin tersine bölerek bölmeye değiştirmek, performansı mütevazı bir şekilde düşürebilir (en azından biri -O3-hızlı yolunu geçmeye çalışmazsa). Benzer şekilde çalışmayı arttırmak için birleşim kullanarak ( exp(T*(r-0.5*v*v))olma exp(T*r - T*v*v/2.0); exp(sqrt(v*v*T)*gauss_bm)olma exp(sqrt(v)*sqrt(v)*sqrt(T)*gauss_bm)). Birliktelik (ve genelleme) ayrıca exp(T*r - T*v*v/2.0)`` pow ((pow (e_value, T), r) / pow (pow (pow ((pow (e_value, T), v), v)), - 2.0) [veya başka bir şeye ”dönüşebilir. Bu matematik hileleri gerçekten mikro mimari deoptimizasyonlar olarak sayılmaz
Paul A. Clayton

2
Bu yanıtı gerçekten takdir ediyorum ve Agner's Fog çok yardımcı oldu. Bu öğütmeye izin vereceğim ve bu öğleden sonra üzerinde çalışmaya başlayacağım. Bu, gerçekte neler olup bittiğini öğrenmek açısından muhtemelen en yararlı ödev olmuştur.
Cowmoogun

19
Bu önerilerin bazıları o kadar şeybilimsiz ki, şu anda 7 dakikalık çalışma süresinin çıktıyı doğrulamak için oturmak istemesi için çok fazla olup olmadığını görmek için profesörle konuşmam gerekiyor. Hala bununla çalışırken, bu muhtemelen bir projede yaşadığım en eğlenceli şeydi.
Cowmoogun

4
Ne? Muteks yok mu? Her bir hesaplamayı koruyan bir muteks ile eşzamanlı olarak çalışan iki milyon iş parçacığına sahip olmak (her ihtimale karşı!) Gezegendeki en hızlı süper bilgisayarı dizlerine getirecektir. Bununla birlikte, bu şeytani beceriksiz cevabı seviyorum.
David Hammen

35

İşlerin mümkün olduğunca kötü performans göstermesi için yapabileceğiniz birkaç şey:

  • i386 mimarisinin kodunu derler. Bu, SSE ve daha yeni talimatların kullanılmasını önleyecek ve x87 FPU'nun kullanımını zorlayacaktır.

  • std::atomicdeğişkenleri her yerde kullanın . Bu, derleyicinin her yere bellek engelleri yerleştirmeye zorlanması nedeniyle onları çok pahalı hale getirecektir. Ve bu, beceriksiz bir kişinin "iplik güvenliğini sağlamak" için makul bir şekilde yapabileceği bir şeydir.

  • önceden getiricinin tahmin edebileceği en kötü şekilde belleğe eriştiğinizden emin olun (sütun major ve satır major).

  • değişkenlerinizi daha pahalı hale getirmek newiçin, 'otomatik depolama süresine' (yığma ayrılmış) izin vermek yerine, bunları ayırarak hepsinin 'dinamik depolama süresi' (yığın ayrılmış) olduğundan emin olabilirsiniz .

  • ayırdığınız tüm belleğin çok garip bir şekilde hizalandığından ve elbette çok büyük sayfalar ayırmaktan kaçının, çünkü bunu yapmak çok fazla TLB verimli olacaktır.

  • ne yaparsanız yapın derleyicinizi optimize edici etkinken kodunuzu oluşturmayın. Ve yapabileceğiniz en etkileyici hata ayıklama simgelerini etkinleştirdiğinizden emin olun (kodun daha yavaş çalışmasını sağlamaz , ancak fazladan disk alanını boşa harcar).

Not: Bu cevap temelde @Peter Cordes'in zaten çok iyi cevabına dahil ettiği yorumlarımı özetliyor. Eğer yedek bir tane varsa o sizin oyunuzu almak öneririz :)


9
Bunlardan bazılarına temel itirazım şu ifadeyi içermesidir : Programı deoptimize etmek için Intel i7 boru hattının nasıl işlediğine ilişkin bilginizi kullanın . X87 hakkında uarch'a özgü bir şey std::atomicveya dinamik ayırmadan fazladan bir dolaylama seviyesi olduğunu hissetmiyorum . Atom veya K8'de de yavaş olacaklar. Hala oy kullanıyorum, ama bu yüzden bazı önerilerinize karşı koydum.
Peter Cordes

Bunlar adil noktalar. Ne olursa olsun, bu şeyler hala bir şekilde askerin hedefine doğru çalışıyor. Upvote için teşekkür ederiz :)
Jesper Juhl

SSE birimi 0, 1 ve 5 numaralı bağlantı noktalarını kullanır. X87 birimi yalnızca 0 ve 1 numaralı bağlantı noktalarını kullanır.
Michas,

@Michas: Bu konuda yanılıyorsun. Haswell, 5. bağlantı noktasında SSE FP matematik talimatlarını çalıştırmaz. Çoğunlukla SSE FP karıştırır ve boolelar (xorps / andps / orps). x87 daha yavaş, ancak nedeninizle ilgili açıklamanız biraz yanlış. (Ve bu nokta tamamen yanlış.)
Peter Cordes

1
@Michas: movapd xmm, xmmgenellikle bir yürütme bağlantı noktasına ihtiyaç duymaz (IVB ve sonraki sürümlerde kayıt yeniden adlandırma aşamasında işlenir). AVX kodunda neredeyse hiçbir zaman gerekli değildir, çünkü FMA dışındaki her şey yıkıcı değildir. Ama yeterince adil, Haswell elenmezse port5 üzerinde çalıştırır. X87 register-copy ( fld st(i)) 'a bakmamıştım , ama Haswell / Broadwell için haklısın : p01 üzerinde çalışıyor. Skylake p05 üzerinde çalışır, SnB p0 üzerinde çalışır, IvB p5 üzerinde çalışır. IVB / SKL p5 üzerinde bazı x87 şeyler (karşılaştırma dahil) yapar, ancak SNB / HSW / BDW x87 için hiç p5 kullanmaz.
Peter Cordes

11

long doubleHesaplama için kullanabilirsiniz . X86'da 80 bit formatı olmalıdır. Sadece eski olan x87 FPU'nun buna desteği var.

X87 FPU'nun birkaç eksikliği:

  1. SIMD eksikliği, daha fazla yönerge gerektirebilir.
  2. Yığın tabanlı, süper skaler ve boru hatlı mimariler için sorunlu.
  3. Ayrı ve oldukça küçük kayıt kümeleri, diğer kayıtlardan daha fazla dönüştürmeye ve daha fazla bellek işlemine ihtiyaç duyabilir.
  4. Core i7'de SSE için 3 ve x87 için sadece 2 bağlantı noktası vardır, işlemci daha az paralel talimatlar yürütebilir.

3
Skaler matematik için, x87 matematik komutlarının kendileri sadece biraz daha yavaştır. Bununla birlikte, 10 bayt işlenenlerin depolanması / yüklenmesi oldukça yavaştır ve x87'nin yığın tabanlı tasarımı ekstra talimatlar (örneğin fxch) gerektirme eğilimindedir . Bununla birlikte -ffast-math, iyi bir derleyici monte-carlo döngülerini vektörize edebilir ve x87 bunu önleyecektir.
Peter Cordes

Cevabımı biraz uzattım.
Michas

1
re: 4: Hangi i7 uarch hakkında konuşuyorsunuz ve hangi talimatlar? Haswell p01 üzerinde çalışabilir mulss, ancak fmulsadece üzerinde çalışır p0. addsssadece ile p1aynı şekilde çalışır fadd. FP matematik işlemlerini yürüten yalnızca iki yürütme bağlantı noktası vardır. (Bunun tek istisnası, Skylake'in özel ekleme birimini düşürmesi ve addssp01'de, ancak p5'te FMA birimlerinde çalışmasıdır fadd. Bu nedenle, bazı faddtalimatlarla birlikte karıştırarak fma...ps, teorik olarak biraz daha fazla toplam FLOP / sn yapabilirsiniz.)
Peter Cordes

2
Ayrıca Windows x86-64 ABI'nin 64bit olduğunu unutmayın long double, yani hala sadece double. SysV ABI 80 bit kullanıyor long double. Ayrıca, re: 2: kayıt yeniden adlandırma yığın kayıtlarında paralellik ortaya çıkarır. Yığın tabanlı mimari fxchg, esp gibi bazı ek talimatlar gerektirir . paralel hesaplamalarda serpiştirirken. Bu yüzden, uarch'ın orada olanı sömürmesi zor olmaktan ziyade, bellek gidiş-dönüşleri olmadan paralellik ifade etmek daha zor gibi. Yine de, diğer reglardan daha fazla dönüştürmeye ihtiyacınız yok. Ne demek istediğinden emin değilim.
Peter Cordes

6

Geç cevap, ancak bağlantılı listeleri ve TLB'yi yeterince kötüye kullandığımızı düşünmüyorum.

Çoğunlukla adresin MSB'sini kullanacak şekilde düğümlerinizi ayırmak için mmap kullanın. Bu, uzun TLB arama zincirleri ile sonuçlanmalı, sayfa 12 bit, çeviri için 52 bit veya her seferinde geçmesi gereken yaklaşık 5 seviye olmalıdır. Biraz şansla, her seferinde 5 seviyeli arama ve düğüme ulaşmak için 1 bellek erişimi için belleğe gitmelidir, üst düzey büyük olasılıkla bir yerde önbellekte olacaktır, bu yüzden 5 * bellek erişimi için umut edebiliriz. Düğümü, sonraki işaretçiyi okurken başka bir 3-4 çeviri aramasına neden olacak şekilde en kötü kenarlığı atacak şekilde yerleştirin. Bu, çok fazla çeviri araması nedeniyle önbelleği tamamen bozabilir. Ayrıca sanal tabloların boyutu, kullanıcı verilerinin çoğunun diske fazladan bir süre disk belleği olarak yerleştirilmesine neden olabilir.

Tek bağlantılı listeden okurken, tek bir sayının okunmasında maksimum gecikmeye neden olmak için her seferinde listenin başından okuduğunuzdan emin olun.


x86-64 sayfa tabloları, 48 bit sanal adresler için 4 düzey derinliğindedir. (Bir PTE'nin 52 bit fiziksel adresi vardır). Gelecekteki CPU'lar 9 bitlik sanal adres alanı için 5 seviyeli bir sayfa tablosu özelliğini destekleyecektir (57). Neden 64bit'te sanal adres fiziksel adresle (52 bit uzunluğunda) 4 bit kısa (48 bit uzunluğunda)? . İşletim sistemleri varsayılan olarak etkinleştirmez, çünkü daha yavaş olur ve bu kadar adres adres alanına ihtiyaç duymadıkça fayda sağlamaz.
Peter Cordes

Ama evet, eğlenceli bir fikir. Belki kullanabilirsiniz mmapfiziksel RAM aynı miktarda üzerinde daha fazla TLB özlüyor sağlayan (aynı içerikli) aynı fiziksel sayfa için birden fazla sanal adreslerini almak için bir dosya veya paylaşılan bellek bölge üzerinde. Bağlantılı listeniz nextyalnızca göreli bir ofset olsaydı, +4096 * 1024nihayet farklı bir fiziksel sayfaya ulaşana kadar aynı sayfanın bir dizi eşlemesine sahip olabilirsiniz . Veya elbette L1d önbellek isabetlerini önlemek için birden fazla sayfaya yayılıyor. Sayfa yürüyüşü donanımında daha üst düzey PDE'lerin önbelleğe alınması vardır, bu yüzden evet onu neredeyse addr alanına yayın!
Peter Cordes

Eski adrese bir ofset eklemek, [ [reg+small_offset]adresleme modu için özel durum ] 'u yenerek yük kullanım gecikmesini daha da kötüleştirir ( taban + ofset, tabandan farklı bir sayfada olduğunda bir ceza var mı? ); add64 bitlik bir ofset bellek kaynağı alırsınız veya gibi bir yük ve dizinlenmiş adresleme modu alırsınız [reg+reg]. Ayrıca bkz. L2 TLB kaçırıldıktan sonra ne olur? - sayfa yürüyüşü SnB ailesindeki L1d önbellek yoluyla getirilir.
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.