- Yakın zamanda tartışılan I/O performansı ile CPU işlem hızı arasındaki dengesizlik deneylerle doğrulanıyor ve gerçekte ana kısıtın hâlâ CPU olduğu gösteriliyor
- Sıralı okuma hızı, soğuk önbellekte 1.6GB/s, sıcak önbellekte 12.8GB/s seviyesine ulaşsa da, tek iş parçacıklı kelime frekansı hesabı yalnızca 278MB/s düzeyinde kalıyor
- Koddaki dal (branch) yapısı vektörleştirmeyi (vectorization) engelliyor ve yalnızca basit bir küçük harfe dönüştürme optimizasyonuyla bile ancak yaklaşık 330MB/s seviyesine çıkılabiliyor
wc -w komutu bile yalnızca 245MB/s olduğundan, darboğazın diskten çok CPU hesaplaması ve dal işleme olduğu doğrulanıyor
- AVX2 tabanlı elle vektörleştirme ile hız 1.45GB/s seviyesine çıkarıldı, ancak bu bile hâlâ sıralı okuma hızının yaklaşık %11'i düzeyinde; böylece darboğazın I/O değil, CPU olduğu kanıtlanıyor
I/O hızı ile CPU performansının karşılaştırılması
- Ben Hoyt'un iddiasından yola çıkılarak, son dönemdeki sıralı okuma hızı artışının CPU hızındaki durgunluğu geçip geçmediği test ediliyor
- Aynı yöntemle ölçüldüğünde soğuk önbellekte 1.6GB/s, sıcak önbellekte 12.8GB/s elde edildi
- Ancak tek iş parçacığında kelime frekansı hesabı yapıldığında hız yalnızca 278MB/s oldu
- Bu, önbellek sıcak olsa bile disk okuma hızının yaklaşık beşte biri düzeyinde
C tabanlı kelime frekansı hesaplama deneyi
optimized.c, GCC 12 ile -O3 -march=native seçenekleri kullanılarak derlenip 425MB giriş dosyasında çalıştırıldı
- Sonuç: 1.525 saniye, 278MB/s işlem hızı
- Kod içindeki çoklu dallar ve erken çıkışlar, derleyicinin vektörleştirme optimizasyonunu engelliyor
- Küçük harfe dönüştürme mantığı döngü dışına taşındıktan sonra hız 330MB/s seviyesine çıktı
- Clang kullanıldığında vektörleştirme daha iyi yapılıyor
Basit kelime sayımı (wc -w) karşılaştırması
- Frekans hesabı yerine yalnızca toplam kelime sayısını sayan
wc -w komutu çalıştırıldı
- Sonuç: 245.2MB/s, beklenenden yavaş
wc, ' ', '\n', '\t' gibi çeşitli boşluk karakterlerini ve yerel ayar karakterlerini işliyor
- Bu yüzden yalnızca boşluğu ayırıcı kabul eden koda göre daha fazla işlem yapıyor
AVX2 tabanlı vektörleştirme denemesi
- Modern CPU özelliklerinden yararlanılarak AVX2 komut kümesi ile vektörleştirme uygulandı
- 256 bit yazmaç kullanıldı, veri 32 bit hizalandı
- Boşluk karakteri karşılaştırmaları için
VPCMPEQB komutu kullanıldı
- Bit maskesi (PMOVMSKB) ve Find First Set (ffs) komutlarıyla kelime sınırları tespit edildi
- Fikir, Cosmopolitan libc içindeki
strlen uygulamasından alındı
Performans sonuçları ve sonuç
- Elle vektörleştirilmiş kod (
wc-avx2) 1.45GB/s işlem hızına ulaştı
wc -w ile aynı sonuç (82,113,300 kelime) doğrulandı
- Soğuk önbellek durumunda bile hâlâ user modundaki işlem süresi baskın
- Darboğazın disk I/O'sundan çok CPU hesaplaması olduğu doğrulandı
- Genel olarak disk hızı yeterince yüksek, ancak dal işleme ve hash hesaplama gibi CPU işlemleri sınırlayıcı etken olmaya devam ediyor
- Kod ve deney sonuçları GitHub'da (
haampie/wc-avx2) yayımlandı
1 yorum
Hacker News yorumları
Modern CPU'larda performans sınırının, tek bir çekirdeğin işleyebildiği veri miktarı, yani
memcpy()hızı tarafından belirlendiğini düşünüyorÇoğu x86 çekirdeği yaklaşık 6GB/s, Apple M serisi ise yaklaşık 20GB/s seviyesinde
Reklamlarda söylenen “200GB/s” gibi rakamlar yalnızca tüm çekirdeklerin toplam bant genişliği; tek çekirdek hâlâ 6GB/s civarında kalıyor
Bu nedenle mükemmel bir parser yazsanız bile bu sınırı aşamazsınız
Ancak zero-copy formatlar kullanılırsa CPU gereksiz verileri atlayabildiği için teorik olarak 6GB/s'nin “üzerine” çıkılabilir
Geliştirdiği Lite³ formatı bu ilkeyi kullanıyor ve simdjson'dan 120 kata kadar daha hızlı performans gösteriyor
Örneğin Zen 1 tek çekirdekte 25GB/s gösteriyor (referans bağlantı)
Kendisinin yazdığı microbenchmark sonuçlarına göre Zen 2, AVX kullanılmadığında 17GB/s, non-temporal AVX kullanıldığında ise 35GB/s'ye kadar çıkıyor
Apple M3 Max'te non-temporal NEON ile 125GB/s'ye kadar ölçülmüş
Dolayısıyla x86 için 6GB/s, Apple için 20GB/s rakamları gerçeğin oldukça altında
Çünkü iGPU birleşik belleğe erişebiliyor
Bu yüzden büyük bellek kopyalamaları, paralel parsing, sıkıştırma/açma gibi işlerde iGPU'yu bir blitter olarak kullanmak teknik açıdan avantajlı
Yine de zero-copy formatlarda sözü edilen “atlama” cache line düzeyinde gerçekleşiyor
Orijinal yazının yazarının
timekomutunun çıktısını yanlış yorumlamış gibi göründüğünü söylüyorsystemzamanı, kernelin süreç adına kullandığı CPU süresidirÖrnekte
real0.395s,user0.196s,sys0.117s ise CPU toplamda yalnızca 313ms çalışmış, kalan 82ms ise boşta kalmıştırYani disk alt sisteminden daha hızlı çalışmış olsa da aradaki fark büyük değildir
Ayrıca I/O yolu CPU-bound durumda — disk ve kod sonsuz hızlı olsa bile kernel I/O kodunu çalıştırmak için 117ms gerekir
Yazının yazarı kendisi olduğunu ve bir devam yazısı bulunduğunu söylüyor: I/O is no longer the bottleneck, part 2
Katılımcıların kullandığı çeşitli optimizasyon tekniklerini ele alan analiz yazısını ilgi çekici buluyor
Problemin karmaşıklığına veya boşluk karakteri sınıflandırmalarının sayısına göre yaklaşım değişmiş
Performans darboğazının her zaman “CPU mu I/O mu” gibi tek bir etken değil, gerçek iş yükünde ilk önce doygunluğa ulaşan kaynak olduğunu söylüyor
Bu kaynak CPU, bellek bant genişliği, cache, disk, ağ, lock ya da gecikme olabilir
Bu yüzden ölçmek, profiling ile kanıtlamak ve değişiklikten sonra yeniden ölçmek gerekir
Sorunun CPU ya da I/O değil, latency ile throughput arasındaki denge olduğunu söylüyor
Çoğu yazılım gecikmeyi göz ardı ettiği için yavaş
Veriyi bellekte doğrusal yerleştirmek ya da batch processing ve paralellik uygulamak çok daha yüksek hız sağlayabilir
CPU ↔ cache ↔ kalıcı depolama yapısından oluşan bir mimari hayal ediyor
Eğer
mmap()malloc()ile aynı performans özelliklerine sahip olsaydı, program belleği dosya adıyla tanımlanıp kalıcılık OS'e bırakılabilirdiHâlâ birçok yazılım tasarımı sabit disk çağının kısıtlarına bağlı durumda
fsync()hâlâ yavaşGerçek kalıcılık için döner disk olup olmamasından bağımsız olarak farklı bir yaklaşım gerekiyor
Nitekim çoğu bellek isteği
mmap()üzerinden gerçekleşiyorAncak kernel erişim desenlerini tahmin etmekte zorlandığı için
read/write'dan daha yavaş olabilirBulut ortamında performans bazen fiyatlandırmayı ayarlama aracı hâline gelebiliyor
Donanım performansı şaşırtıcı derecede ilerledi ama bazı yazılımlar, özellikle Windows ya da mesajlaşma uygulamaları, buna rağmen daha yavaş hissediliyor
Geliştirici uzaktan çalışma istasyonu olarak verimsiz
Telegram ve FB Messenger hızlı, ama Teams ve Skype öyle değil
Bazı LCD'lerde 500ms gecikme var
NVMe SSD'ler ilk çıktığında “artık 2TB RAM'imiz var” diye şaka yaptığını söylüyor
Ancak bugün GPU sunucuları gerçekten 2TB RAM taşıyor — etkileyici bir mühendislik başarısı
Keşke o zaman alsaydım diye hayıflanıyor
OLAP veritabanı optimizasyonunu yüksek eşzamanlılık ortamında yapma deneyimine göre darboğaz çoğunlukla bellek hızıydı
I/O darboğazı kavramının aslında ardışık okuma ile değil, seek süresiyle ilişkili olduğunu söylüyor
Yazının ana fikrini anladığını ama bu noktayı belirtmek istediğini ekliyor
Ardışık okuma hızı kodla iyileştirilemediği için asıl önemli olan ardışık olmayan erişimin optimize edilmesiydi