En Hızlı Branchless Binary Search
(mhdm.dev)sb_lower_bound,std::lower_boundile aynı arayüzü korurken, karşılaştırma dalı koşullu taşıma (cmov) olarak derlendiğinde standart binary search’e göre 2 kata kadar daha hızlı sonuçlar gösteriyor- Binary search’te karşılaştırma sonucunda arama konumu önceden bilinemediği için branch prediction hataları sık yaşanıyor; x86’da
clang -mllvm -x86-cmov-converter=falseseçeneği bunu azaltmaya yardımcı oluyor - Bu uygulama, her döngüde
lengthdeğerini yarıya indiriyor ve karşılaştırma sonucuna göre yalnızcafirstdeğerini güncelleyerek komut sayısını azaltıyor;2^k <= n < 2^(k+1)aralığında her zamank+1karşılaştırma yapıyor clang -cmovbenchmark’ında ortalama çalışma süreleristd::lower_boundiçin 61.30ns,sb_lower_boundiçin 33.24ns,bb_lower_boundiçin 32.73ns oldu; geometrik ortalamalar da sırasıyla 39.17ns, 19.81ns ve 21.33ns ile büyük fark gösterdi- Karşılaştırma fonksiyonunun yavaş olduğu 8 baytlık string aramalarında
std::lower_bound’un biraz önde olduğu durumlar vardı; büyük dizilerde ise prefetching eklenen varyant,std::lower_bound’dan ortalamada yaklaşık 2.3 kat daha hızlıydı
sb_lower_bound’un temel yapısı
sb_lower_bound,std::lower_boundile aynı biçimde bir C++ fonksiyonudur- Girdileri
first,last,value,comp - Dönüş değeri, karşılaştırmanın ilk kez başarısız olduğu konumdaki iterator’dır; tüm elemanlar koşulu sağlıyorsa
lastdöndürür
- Girdileri
- Temel döngü
lengthdeğerini yarıya indirir ve yalnızcacomp(first[length], value)doğru olduğundafirstdeğerini ileri taşır - Burada “branchless”,
if’in ortadan kalktığı anlamına değil, ilgiliif’in koşullu jump yerinecmovgibi koşullu taşıma komutuna derlendiği duruma işaret eder clang’de-mllvm -x86-cmov-converter=falseseçeneği kullanılırsa bu biçim koşullu taşımaya derlenebilir
std::lower_bound’un yavaşladığı nokta
- Standart binary search, ortadaki elemanı
valueile karşılaştırdıktan sonra sol veya sağ aralığı seçer - Aranan hedefin konumu bilinmediğinde
if (comp(first[half], value))genellikle tahmin edilmesi zor bir branch haline gelir - CPU, branch prediction ile sonraki komutları önceden çalıştırır; ancak tahmin yanlışsa yapılan işi atmak zorunda kalır
- Koşullu taşıma kullanıldığında, karşılaştırma sonucuna göre değer seçilirken koşullu jump’lar azaltılabilir
clang -cmov,std::lower_boundiçindeki bazıif/elseyapılarını da koşullu taşımaya çevirebildiği için yaklaşık %25 hızlanma sağladıgcc’de aynı durumda koşullu taşımayı zorlayan iyi bir seçenek yoktur vesb_lower_boundda mevcut optimizasyon seviyesinden bağımsız olarak branchless kod üretmez
Karşılaştırma sayısı açısından “optimal” arama
- Buradaki “optimal”, karşılaştırma sayısı en az olan binary search anlamına gelir
- Boyutu
nolan bir listedestd::lower_boundiçin olası sonuçlar,nadet eleman konumu ve 1 adet son konum olmak üzere toplamn+1adettir - Liste boyutu
2^k - 1ise olası sonuç sayısı2^kolur ve her karşılaştırma doğru/yanlış biçiminde 1 bit bilgi verdiğinden optimal karşılaştırma sayısıkolur - Uzunluğu
2^k - 1olan “nice” durumlarda çok kısa bir döngüyle optimal arama mümkündür - Uzunluk uygun değilse,
[0, 1, 2, 3, 4, 5]içindevalue4 olduğunda olduğu gibi aralık dışı erişim oluşabilir
sb_lower_bound’un performans özellikleri ve sınırlamaları
sb_lower_bound, uzunluğu çift olan bir aralığı bölerken karşılaştırma sonucu doğru olsa bile bazı durumlarda yeterince fazla elemanı atlamaz2^k <= n < 2^(k+1)aralığında her zamank+1karşılaştırma yapar- Aynı aralıkta
std::lower_bound,kveyak+1karşılaştırma yapar ve ortalama olarak yaklaşıklog2(n+1)kez karşılaştırır - Karşılaştırma sayısı daha fazla olabilir; ancak döngü içindeki komut sayısı çok daha az olduğu için toplam çalışma süresi daha hızlı çıkar
- Karşılaştırma fonksiyonu çok yavaşsa
k+1ilelog2(n+1)arasındaki fark performansı etkileyebilir gcc’de koşullu taşımayı zorlamak için x86’ya özel inline assembly ilecmovkullanmak mümkündür; ancak basit yaklaşım komut sayısını artırır, alternatif yaklaşımda ise her tip için ayrı assembly yazmak gerekir
Daha hızlı varyant: bb_lower_bound
bb_lower_bound, uzunluk2^k - 1biçimine gelene kadar aralığı farklı bir yöntemle böler, ardından hızlı ikinci döngüyle arama yaparlength & (length + 1), uzunluğun11..1biçiminde, yani2^k - 1olup olmadığını belirlemek için kullanılır- Normal olmayan uzunluklarda “nice” bir aralığa hızla yaklaşmak için
auto step = length / 8 * 6 + 1şeklinde bir MAGIC değer kullanılır - Hızlı döngüye sık geçebilmek için
stepgenelliklelength / 2veya daha büyük olmalıdır; ancaklengthdeğerine çok yakın olursa binary search’ün avantajı kaybolur breaknedeniylebb_lower_boundbranch içeren bir biçime dönüşür- Tüm uzunluklar için en hızlı
stepdeğerlerini önceden hesaplanmış bir tabloda kullanma yaklaşımı henüz keşfedilmemiş bir yol olarak duruyor
Tamamen branchless uygulama daha hızlı olmadı
- 64 bit makinelerde
sb_lower_bounddöngüsü en fazla 64 kez yinelendiği için,switchve kasıtlı fall-through kullanaraklengthkontrolünü de kaldıran “tamamen branchless” bir sürüm yapılabilir - Bu yaklaşım,
std::bit_width(length)ile gereken karşılaştırma sayısı kadar kod konumuna atlayan bir yapıdır - Gerçek performans daha hızlı çıkmadı
- Modern x86 CPU’lar döngü koşulu gibi tahmin edilebilir branch’leri iyi işlediğinden,
lengthkontrolünü kaldırmanın faydası olmadı - Template, makro ve 64 case’in kopyalanıp değiştirilmesinden kaçınılabilmesi açısından da standart döngünün daha iyi olduğuna karar verildi
Benchmark sonuçları
- Ortalama çalışma süresi (ns) açısından
clang -cmovtemelindeki sonuçlar şöyleydistd::lower_: 61.30branchless_lower_: 43.43asm_lower_: 54.32sb_lower_: 33.24sbm_lower_: 35.54bb_lower_: 32.73
- Geometrik ortalama çalışma süresinde (ns) de
sb_lower_en düşük değere sahiptistd::lower_: 39.17branchless_lower_: 25.14asm_lower_: 31.21sb_lower_: 19.81sbm_lower_: 20.91bb_lower_: 21.33
sbm_lower_bound,ifyerinefirst += comp(first[length], value) * (length + rem)biçimini kullanarakgcc’nin koşullu taşıma üretmesini teşvik eden bir varyanttır- Bu optimizasyon sonraki
gccsürümünde kaybolabileceğinden yorum ve dikkat gerektirir - Benchmark komutlarında
g++-10,clang++-10,clang++-10 -mllvm -x86-cmov-converter=falsekullanıldı ve-march=haswelleklendi -march=nativeveya-marchbelirtilmemesi sıralamayı belirgin biçimde etkilemedi; testler Intel i7 Kaby Lake üzerinde yürütüldü
Branch prediction hatası ölçümü
perfile ölçülen standartclangçalıştırması yaklaşık 6.94 milyar branch ve yaklaşık 1.20 milyar branch-miss kaydetti; branch-miss oranı %17.34 olduclang -cmovçalıştırması yaklaşık 4.07 milyar branch ve yaklaşık 35.95 milyon branch-miss kaydetti; branch-miss oranı %0.88’e düştü-cmov, yaklaşık 2.9 milyar branch’i ve yaklaşık 1.2 milyar branch hatasını ortadan kaldırdı- Ortadan kaldırılan branch’ler yaklaşık %41 olasılıkla yanlış tahmin edilen branch’lerdi
- Bu değer, tamamen tahmin edilemeyen branch’lerde beklenebilecek %50’ye yakın bir değerdir
Yavaş karşılaştırma fonksiyonlarında sonuç değişiyor
- Karşılaştırma fonksiyonunun daha yavaş olduğu durumu görmek için 8 baytlık string araması test edildi
- Ortalama çalışma süresinde (ns),
std::lower_boundsb_lower_bound’dan biraz daha hızlı veya ona yakındıgcc:std::lower_160.01,sb_lower_165.66clang:std::lower_157.71,sb_lower_162.68,bb_lower_157.22clang -cmov:std::lower_156.06,sb_lower_164.71,bb_lower_157.48
- Bu durumda
std::lower_bound,sb_lower_bound’dan çok az ama tutarlı biçimde daha hızlıdır - Bir kütüphane, ham tipler üzerinde doğrudan çalışırken
sb_lower_boundkullanıp diğer durumlardastd::lower_boundkullanarak en iyi performansı hedefleyebilir
Assembly’de görülen farklar
std::lower_bound’unclang -cmovhot loop’ucmova,cmovbegibi koşullu taşıma komutları içerir; ancak uzunluk ve konum güncellemesi için birden fazla komut kullanırsb_lower_bound’un hot loop’u yarım uzunluğu, kalanı ve taşınacak pointer’ı hesapladıktan sonracmovailefirstdeğerini güncellerbranchless_lower_bound’un assembly’si çok kısa ve temizdir; ancak performans testlerindesb_lower_bounddaha düşük overhead ile daha iyi sonuç verdi
Güncelleme: kısaltılmış sb_lower_bound
- orlp.net yazarı tarafından yapılan yorumdan sonra
sb_lower_bound, hot loop assembly komut sayısını 9’dan 8’e düşürecek şekilde refactor edilebilir - Temel nokta,
length - halfdeğerininhalf + length % 2ile aynı olmasıdır - Refactor edilmiş biçim
half = length / 2değerini hesaplar; karşılaştırma doğruysafirst += length - halfyapar, ardındanlength = halfolarak günceller clang -cmovile ortalama çalışma süresi yaklaşık 33ns’den yaklaşık 32ns’ye küçük bir iyileşme gösterdi
Büyük dizilerde prefetching etkili
- Yorumlarda önerilen prefetching, gerekli belleği önceden L1/L2 cache’e getirerek gerçek erişim sırasındaki gecikmeyi azaltma yöntemidir
- Örnek gecikmeler L1 için yaklaşık 4 cycle, L2 için yaklaşık 12 cycle, L3 için yaklaşık 40 cycle, bellek için yaklaşık 200 cycle’dır
- Hem
gcchem declang__builtin_prefetch()destekler length / 4konumunu prefetch etmek 2 erişimden 1’ini boşa harcar; bunalength / 8de eklenirse 6 erişimden 5’i boşa gider- Prefetch konumunun hesaplanması ve çağrının kendisi de overhead oluşturur; kısaltılmış hot loop’ta bu maliyet önemlidir
- Çeşitli prefetch stratejileri 256KB’den küçük dizilerde fayda sağlamadı
- 256KB ve üzerindeki dizilerde prefetching eklenmiş
sbp_lower_bound, yaklaşık 4 milyon entry’ye, yani 16MB’ye kadar olan testlerde ortalama çalışma süresini yaklaşık 32ns’den yaklaşık 26ns’ye iyileştirdi - Daha sonra yaklaşık 128 milyon entry’ye, yani 512MB’ye kadar genişletilen testlerde prefetch sürümü,
std::lower_bound’a göre ortalama süre bazında yaklaşık 2.3 kat daha hızlıydı- Karşılaştırma ölçütü
std::lower_boundiçin yaklaşık 161ns, prefetch sürümü için yaklaşık 71ns idi
- Karşılaştırma ölçütü
Büyük veri kümelerinde gözlemler ve alternatifler
- Çok büyük boyutlarda
clang -cmovtarafından üretilen branchlessstd::lower_bound, branch içeren sürümden daha yavaştı - Modern CPU’lar tahmin edilmiş branch’i izleyerek bellek yüklemeleri ve speculative execution yapabilir; bu da fiilen prefetch gibi çalışabilir
sbpm_lower_bound,sbm_lower_bound’a prefetching eklenmiş sürümdür vegcc’nin branchless kod üretmesini teşvik etmek için boolean çarpımı kullanır- 1 milyon ile 10 milyon eleman arasında performans grafiğinde sıçramalar olduğu için teorik olarak daha hızlı bir uygulama alanı vardır
- Ancak prefetching kodu giderek karmaşıklaşır ve magic constant sayısı artar; karmaşıklık yükseldikçe
gcc/libstdc++veyallvm/libc++’a katkı olarak kabul edilme olasılığının düştüğü düşünülüyor std::lower_boundkısıtlarını aşan bir alternatif olarak Eytzinger Binary Search vardır; giriş dizisini binary orta değer heap’i biçiminde yeniden düzenleyerek cache dostu arama yapar- Sergey Slotin’in CppCon 2022 int 16-ary tree testinde
std::lower_bound’dan 7 ila 15 kat hızlı sonuçlar elde edildi
Kod ve kullanım koşulları
- Arama veya karşılaştırma programın en yavaş kısmıysa ve işlemcinin karşılaştırma sonucunu tahmin etmesi zorsa x86’da
clang’in-mllvm -x86-cmov-converter=falseseçeneği denenebilir - Daha hızlı binary search gerekiyorsa
sb_lower_bounddenenebilir;gcciçinsbm_lower_boundda bir seçenektir - Kod MIT lisansıyla yayımlanmıştır
- Kod ve benchmark’lara github.com/mh-dm/sb_lower_bound/ adresinden ulaşılabilir
1 yorum
Hacker News yorumları
İnsanların dallanmayı ortadan kaldırmayı denediğini her gördüğümde, dallanma tahmin hatasının uzun bir işlem hattını durdurmasına yol açan yapının CPU mimarisinin zorunlu bir unsuru olmadığının farkında olup olmadıklarını merak ediyorum.
İşlem hattının uzun olmasının nedeni, yürütmeden hemen önce çok sayıda analiz ve dönüşüm yapılması; oysa durum bağımlılığı yüksek bir algoritma da olmadığı için bunların çoğu önceden yapılabilir.
Transmeta Crusoe CPU bu şekilde çalışıyordu ve dallanmaları dert etmek zorunda kalmadığımız bir dünya hayal edilebilir.
Daha derine inersek, tüm işlemler bit durumuna bakıp sonucu değiştiren birer dallanmadır; ancak ALU içindeki bu yerel dallanmalar ana işlem hattı üzerindeki dallanmalar olmadığı için performansa büyük zarar vermez.
O zamanlar srk’ye de IPC ile iş hacmi arasında hangi metriği seçtiğinin, neyi iyi neyi kötü gördüğünü etkilediğini söylediğimi hatırlıyorum.
IPC tarafı, daha yüksek IPC elde edilirse üretim sürecinin saat hızını artıracağı ve herkesin kazanacağı görüşünde; iş hacmi tarafı ise Moore yasasının öldüğünü, silikonu daha hızlı çalıştırırsan eriyeceğini, dolayısıyla ISA’yı akıllıca tasarlayan tarafın kazanacağını söyleyen daha gerçekçi bir yaklaşım benimsiyor.
Son 20 yılda iki taraf da hem başarılar hem de hayal kırıklıkları yaşadı; bugünlerde RISC-V’nin CPU mimarisinde bu sorulara geri dönmesi ilginç.
Komut kümesinin esnekliği temelinde modern superscalar fikirlerin nasıl eklendiğini izlemek için de iyi bir yer; uzun vadede kazananın bu taraf olacağını düşünüyorum.
Transmeta’nın dönüşümü dallanma maliyetini ortadan kaldırmadı.
Transmeta’da çalışan Linus’un bir comp.arch başlığında “CPU’nun işi, cache miss’leri mümkün olduğunca hızlı üretmektir” gibi bir şey söylediğini hatırlıyorum.
Zorunlu cache miss’ler vardır ve hiçbir JIT bunları ortadan kaldıramaz.
Gerçek dünyada, bugünkü devasa cache’ler olsa bile kapasite kaynaklı miss’lerden de kaçınılamaz.
Itanium da statik analizle dallanma maliyetinin ortadan kaldırılabileceğini düşünüyordu; sonucunun nasıl olduğunu hatırlamak yeterli.
Programcıların, modern işlemcilerden daha iyisini kolayca yapabileceklerine güvenle hükmetmeden önce biraz bilgisayar mimarisi kitabı okumasını isterdim.
Güncel işlemcilere konan zihinsel emeğin ölçeğini en az 7 basamak kadar küçümsediklerini düşünüyorum.
Bunlardan biri işlenen girdi verisidir.
İkili arama tam da böyle bir durumdur; derleyici sonucun hangi konumda bulunacağını bilmez.
Bir diğeri mikro mimaridir, özellikle cache hiyerarşisi ve yürütme birimlerinin düzeni.
Mevcut CPU’nun mikro işlemlerine benzeyen komutlara sahip bir ISA’ya geçerseniz, her mikro mimari için yeniden derlemeniz gerekir.
Ancak bu, teknik olarak mevcut GPU’larda olduğu gibi programların bytecode biçiminde (DXBC, SPIR-V, NVPTX) dağıtıldığı ve kullanıcı modu GPU sürücüsünün bunları gerçek donanım komutlarına yeniden derlediği türden bir OS JIT ile çözülebilir.
Daha büyük değişken, diğer CPU thread’lerinin bilinmeyen kodlar çalıştırmasıdır.
Hyper-threading’i kaldırıp çekirdekleri bağımsız hale getirseniz bile L3 cache, harici bellek, I/O bant genişliği, güç ve ısı gibi çip genelinde paylaşılan kaynaklar hâlâ kalır.
Her şeyi Branch™ diye yeniden tanımlarsanız, gerçek dallanma olmayan şeyler de dâhil olmak üzere bazı Branch™’ler önceden hesaplanabilir.
Ama genelde dallanmayı ortadan kaldırma dediğimiz şey, if/else gibi kodlarda hesaplama yolunun gerçekten ayrıldığı durumlarla ilgili değil mi?
Böyle bir dünyada da faydalı optimizasyonlar mümkün olurdu; fakat bunlar, birden fazla gelecekteki sonucu aynı anda hesaplamaya çalışan Branch™’lerle sınırlı kalırdı.
Bağımsız olarak yapılabilecek bir işlem olduğunda, onu aynı anda yürütme olasılığı da doğar.
Sadece decode, fetch ve execute’tan bahsetmiyorum.
Bağımsız bir ALU ve shifter varsa, toplama yaparken shift de yapabilirsiniz; ayrı bir toplama birimi ve çarpma birimi varsa ikisini aynı anda denememek için bir neden yoktur.
Bu da birden fazla komutu aynı anda ilerler durumda tutmak istemenize yol açar ve komutları işleme hızından daha hızlı getirip decode edebilmeniz gerektiği anlamına gelir.
Ayrıca N adet Add komutunun bağımsız bir Shift’i görmesini engellememesi için yeniden sıralama yapmak isteyeceğiniz duruma doğal olarak götürür.
Mevcut yapının gerekenden fazla karmaşık olduğunu düşünebilirsiniz; haksız da olmayabilirsiniz.
Yine de bugünkü yapıyı ortaya çıkarmak için muazzam bir mühendislik harcanıyor; bu yol dışında çok daha hızlısının yapılabileceğini düşünüyorsanız, bu iddianın ne kadar doğru olduğunu derinlemesine irdelemek gerekir.
“Tüm bunları yazmak için temiz ve hızlı bir bare-metal dil olsaydı…” kısmında yazar “BUT RUST..” ve “BUT ZIG..” dipnotlarını eklemiş, ama Nim nasıl olurdu merak ediyorum
lowerBoundiçin yerel kütüphane implementasyonu var gibi görünüyor: https://github.com/nim-lang/Nim/blob/version-2-0/lib/pure/al...Tam anlamıyla “bare-metal” bir dil değil ama C veya C++’a derlendiği için burada nasıl bir koda derlendiğini görmek ilginç olabilir
Bir de C’nin sorununun ne olduğunu merak ediyorum
TigerBeetle kendi dalsız implementasyonunu kullanıyor: https://github.com/tigerbeetle/tigerbeetle/blob/e996abcf7154...
C++ şablonlarına tam da bu tür kullanım için ihtiyaç var
C temiz değil
Bunun hâlâ
lower_boundolup olmadığından pek emin değilimKodu yanlış okumuş olabilirim ama tekrarlar olduğunda ilk eşleşeni değil, herhangi bir eşleşeni döndürüyor gibi görünüyor
Karşılaştırma fonksiyonu otomatik tamamlama için belirli bir dize önekini arıyorsa, benzersiz bir listede bile birden fazla öğe eşleşebilir; o durumda listedeki en baştaki öğeyi istersiniz
Neden öyle olmadığını düşündüğünü merak ediyorum
Keşke tüm blog yazıları bu yazı gibi başlasa: “Meşgul olduğunuzu biliyorum, o yüzden doğrudan konuya gireceğim. İşte en hızlı, genel ve basit C++ ikili arama implementasyonu”
Zig standart kütüphanesi ikili arama için C++ çağırmıyor
Mevcut ikili arama burada: https://github.com/ziglang/zig/blob/b835fd90cef1447904d3b009...
Pek anlayamadım
İkili aramada dallanma ile ilgili sorun dallanmanın kendisi değil; karşılaştırmayı bitirene kadar diziden bir sonraki hangi bellek konumunun getirileceğini bilmememiz
Dal kullansanız da başka bir şey kullansanız da, mesele eninde sonunda işlemciden ne yapmasını istediğiniz
Veri bağımlılığı var
Orta indeksi okumadan önce üst aralıkta mı, alt aralıkta mı arama yapacağınızı bilemezsiniz
Tahmin edip iki taraf için de okumaları başlatabilirsiniz; bu bağımlılığı çözer ama bellek trafiğini artırır
Asıl mesele bunun doğru ödünleşim olup olmadığı; sadece dallanmayı kaldırmak cevap değil
Yazının son kısmında ele alınıyor: https://mhdm.dev/posts/sb_lower_bound/#prefetching
Bu yüzden gerçekten daha hızlı ikili arama Eytzinger dizi yerleşimi kullanır: https://algorithmica.org/en/eytzinger
Benim Cascade Lake işlemcimde
-mllvm -x86-cmov-converter=false, ikili arama performansını neredeyse yarıya düşürüyorSayılar, 100MB’lık
uint32dizisinde bsearch başına nanosaniyeclang 15.0.7 bu belirli kod optimizasyonunda gcc 13.2.1’den çok daha kötü görünüyor
Assembly burada görülebilir: https://godbolt.org/z/cbx5Kdjs6
gcc assembly’si çok daha temiz görünüyor
100MB yeterince büyük olduğu için dallı sürüm biraz avantajlı çıkıyor; daha iyi olduğu için değil, x86’nın spekülatif yürütme özellikleri nedeniyle
“BUT RUST” bağlantısının aslında nereye gitmesi gerektiğini bilen var mı?
Sürüm sabitlenmediği için şimdiden bozulmuş gibi; belki de
starts_withdokümantasyon yorumlarının ortasına gitmesi amaçlanmış olabilirlet mid = left + size / 2;[1] https://web.archive.org/web/20230602210213/https://doc.rust-...
Rust’ın ikili arama uygulamasına bağlantı vermek istemiştim
https://doc.rust-lang.org/1.71.1/src/core/slice/mod.rs.html#... olarak güncellendi
Daha karmaşık
compkarşılaştırma işlevlerinde sonucun korunmaması ilginçYazıda ID, telefon numarası, hesap, anahtar kelime gibi karşılaştırma işlevinin yavaş olduğu, nispeten gerçekçi bir ikili arama senaryosu düşünülmüş ve bu yüzden 8 baytlık dizge araması test edilmiş deniyor
Bu durumda
std::lower_bound,sb_lower_bound’dan çok az da olsa tutarlı biçimde daha hızlı; her zaman en iyi performansı almak için kütüphanenin ilkel tipleri doğrudan ele aldığı durumlardasb_lower_bound, diğerlerinde isestd::lower_boundkullanması gerektiği söyleniyorBuradaki analizi görmek isterim
Gerçekten rastgele veri ve girdi söz konusuysa tahminler kabaca yarı yarıya yanlış olur
CMOV yaklaşımı, karşılaştırma işlevinden sonra veri bağımlılığı nedeniyle tıkanır
Ortalama olarak dallanmalı yaklaşım aynı anda iki karşılaştırma yapar, CMOV ise bir tane; bu nedenle karşılaştırma süresi dal tahmini hatası cezasını aştığında bir kırılma noktası oluşması beklenir
Daha önce SIMD ile kabaca yaptığım bir şey, bellek bant genişliğine takılana kadar
std::lower_bound’dan 3 kat hızlıydı: https://github.com/matthewkolbe/ThinkingInSimd/tree/main/alg...Tamamen rastgele olduğunu varsayıyorum, ancak bu 8 baytlık dizgeler saf bilgi değilse modern dal tahminleyiciler
cmovdan kolayca daha iyi performans gösterebilirunpredictableözniteliği artık cmov dönüşüm geçişini etkiliyor gibi görünüyor1 Haziran itibarıyla, muhtemelen clang 17/18’e girecek gibi: https://reviews.llvm.org/D118118