Linux borularında performans düşüşü
(qsantos.fr)writeile Linux borusuna veri yazmak, basit bellek yazmaya göre çok daha yavaş hale geldi; deney ortamında bellek arabelleğine yazma 167GB/s düzeyine karşılık boruya yazma 17GB/s seviyesinde kaldı- Darboğaz tek bir veri kopyalama işlemiyle açıklanamıyor;
pipe_writeiçindeki sayfa ayırma, kilitleme ve çekirdek kopyalama rutinlerinin maliyetleri birlikte birikiyor vmsplice, kullanıcı alanı arabelleğini çekirdeğe kopyalamadan boruya bağlayarak__alloc_pages,_raw_spin_lock_irq,copy_user_enhanced_fast_stringgibi pahalı yolları atlıyor- Fizz Buzz işlem hacmi örneğinde
vmsplicekullanan çözüm tek çekirdekte 60.8GiB/s, çok çekirdekte 208.3GiB/s seviyesine kadar ulaştı; ayrı bir deneydekivmspliceörneği ise 210GB/s kaydetti - Düzeltmeler nedeniyle SIMD kullanmama cezası yorumu güvenilir değil; süreçler arası iletişim de yalnızca L1 önbellekle bitmediği için 167GB/s'yi gerçek boru işlem hacmi olarak beklemek zor
Başlangıç noktası: vmsplicein yarattığı büyük fark
- Bazı programlar, veriyi borular üzerinden daha hızlı taşımak için
vmsplicesistem çağrısını kullanıyor - Code Golf StackExchange'deki Fizz Buzz işlem hacmi yarışmasında çözümler iki büyük gruba ayrılıyor
vmsplicekullanmayan çözümler saniyede birkaç GiB düzeyinde; neil'in çözümü 8.4GiB/s'ye ulaşıyorvmsplicekullanan çözümler tkluck'ın 15.5GiB/s'sinden ais523'ün 60.8GiB/s'sine ve david'in çok çekirdekli 208.3GiB/s'sine kadar çıkıyor
- Yalnızca çekirdek alanı ile kullanıcı alanı arasındaki kopyalamayı azaltma etkisi, tek çekirdek bazında yaklaşık 7 katlık farkı açıklamakta yetersiz kalıyor
- Kendi deneylerinde de ais523'ün çözümü 96.4GiB/s, david'in çözümü 7 çekirdek kullanıldığında 277GB/s, çekirdek başına yaklaşık 40GB/s kaydetti
Temel çizgi: kullanıcı alanında bellek yazma
- Sistem çağrısı olmadan kullanıcı alanı belleğinde 32KiB arabelleği tekrar tekrar kopyalayan Rust programı, deney ortamında 167GB/s kaydetti
- Bu değerin, kullanılan CPU'nun L1 önbellek yazma hızıyla örtüşen bir seviye olduğu kabul ediliyor
- Deney sistemi Ryzen 9 7950X3D, DDR5 6000T/s, Debian 12 ve Linux 6.1.0-18-amd64 ortamıydı
- CPU hafifletme seçenekleri
mitigations=offile devre dışı bırakıldı
ftraceprofillemesinde zamanın büyük bölümünün__memset_avx512_unaligned_ermsiçinde harcandığı görüldü- Ancak düzeltmeler bu yorumu sınırlıyor
- Kesme noktasındaki komut
rep stosidi ve bu AVX-512 komutu değil - Yalnızca AVX2 ve SSE2 kullanılacak şekilde sınırlandığında da işlem hacminin 167GB/s'de kalmasının nedeni, tüm durumlarda
rep stoskullanılmasıydı
- Kesme noktasındaki komut
write ile boruya yazmanın gerçek maliyeti
- Aynı boyuttaki arabelleği
stdout.write()ile boruya yazıppv >/dev/nullile tüketince işlem hacmi 17GB/s'ye düşüyor - Profilleme sonuçlarına göre zamanın büyük bölümü
writesistem çağrısı içinde harcandı; özellikle %95'ipipe_writeiçinde kaldı pipe_writeiçinde sayfa hazırlama, kilitleme ve kopyalama birlikte maliyet oluşturuyor__alloc_pages: toplam zamanın %36'sı; boru için yeni bellek sayfaları hazırlar__mutex_lock.constprop.0: toplam zamanın %25'i; boruya yazma için kilitleme maliyetini oluşturur_raw_spin_lock_irq: toplam zamanın %5'i; boruya yazmayla ilgili kilitleme maliyeti olarak görünürcopy_user_enhanced_fast_string: toplam zamanın yaklaşık %20'si; veriyi kullanıcı alanından çekirdek tarafına kopyalar
pv,spliceile sayfaları/dev/null'a taşıyarak tükettiği için aynı birkaç sayfayı döngüde sürekli yeniden kullanmak zorlaşıyor
Çekirdek kopyalama rutini ve düzeltilmiş yorum
copy_user_enhanced_fast_stringtersine derlendiğinde, büyük arabellek kopyalarınınREP MOVkomutuyla işlendiği görülüyor- Bu işlev C ile değil Linux çekirdeğinin assembly koduyla uygulanmış; yani derleyici optimizasyonu eksikliği değil, bilinçli seçilmiş bir yol
- İlk deneyde kullanıcı alanında
rep movsbdoğrudan çağrılarak 80GB/s elde edilmiş ve bu, çekirdek kopyalama rutininin yaklaşık 2 kat yavaş olduğu yorumuyla ilişkilendirilmişti - Daha sonra yapılan düzeltmeyle deney koşulları değişti
- İki adet 32KiB arabellek L1 veri önbelleğini dolduruyor
- 16KiB arabellek kullanıldığında performans 153GB/s'ye çıkıyor
- Bu yüzden çekirdek kopyalamada vektör komutlarının kullanılmamasının büyük bir ceza olduğu yönündeki ilk yorum güvenilir değil
- Buna rağmen boruya yazmada bellek yönetimi ek yükünün büyük olduğu gerçeği geçerliliğini koruyor
vmsplicein kaçındığı çekirdek yolu
vmsplice, kullanıcı alanındaki tüm arabelleği çekirdeğe kopyalamadan boruya aktararak boru kullanım maliyetini düşürüyor- Francesco'nun
pipes-speed-testiçinde yer alan./writeörneği, sonsuz şekilde'X'yazan minimal örnek olarak kullanılıyor - Bu örnek 210GB/s kaydetti; ancak aynı arabelleği tekrar tekrar
vmsplicee verdiği için tipik bir veri üretme işinden farklı- Sabit bayt akışı değilse yeni verinin arabelleğe doldurulması gerekir
- Bu noktada basit bellek yazmanın üst sınırı olan 167GB/s yeniden ilgili hale gelir
vmspliceyolunda da__mutex_lock.constprop.0için zamanın %37'si harcanıyor- Ancak
writeyolunda görülen__alloc_pages,_raw_spin_lock_irq,copy_user_enhanced_fast_stringgörünmüyor - Bunun yerine
add_to_pipe,import_iovec,iov_iter_get_pages2ana yollar olarak ortaya çıkıyor vevmspliceinwriteın pahalı kısımlarını atlattığını gösteriyor
Geriye kalan sonuçlar ve dikkat edilmesi gerekenler
- Deneylere göre Linux borusuna
writeile yazma yolu, basit bellek yazmaya göre yaklaşık 10 kat daha yavaş - İlk sonuç, boruya yazmada kilitleme maliyetinin ve SIMD bağlamını kaydetme/geri yükleme maliyetinin büyük olduğu;
splicevevmsplicein bunları atlattığı yönündeydi - Düzeltmeden sonra sonuç daha sınırlı okunmalı
- Çekirdeğin bellek yönetimi ek yükü, boru performans düşüşünde hâlâ önemli bir unsur
- Vektör komutlarının kullanılmamasının beklenildiği kadar büyük bir ceza olduğu yorumu doğru değil
- Süreçler arası iletişim yalnızca L1 önbellek üzerinden gerçekleşemeyeceği için 167GB/s'nin gerçek boru okumasını da içeren işlem hacmi olarak yakalanması zor
- Bazı önemli hatalar düzeltildi ve sonuçların güvenilirliği sınırlı olabilir; bu nedenle sayılar yön gösterici olarak yorumlanmalı
- Boru işlem hacmini artırmak için yalnızca sistem çağrısı sayısını azaltmak değil,
writeın geçtiği çekirdek içi yolları ve arabellek işleme biçimini de birlikte değerlendirmek gerekiyor
1 yorum
Hacker News yorumları
Bu sorunu ele almaya çalışan bir yan proje var: https://lwn.net/Articles/976836/
Fikir, desteklenen tüm dosya tanımlayıcıları için bir halka tampon elde etmeyi sağlayan bir sistem çağrısı oluşturmak. Borular da buna dahil; iki uç da halka tampon kullanımını destekliyorsa aynı halka tamponu eşleyip sıfır kopyalı G/Ç yapabilir, bazı durumlarda çekirdek çağrısı bile hiç yapılmayabilir. Birlikte çalışacak kişiler aranıyor
Halka tampon dolu ya da boş olduğunda sleep/poll yapılabiliyor; diğer durumlarda ise kilitsiz ve sistem çağrısı ek yükü olmadan çalışıyor
ringbuffer_wait()depoll()içinde okunabilir durum haline getirilerek sinyal verebilirAnlık bir hiccup bile yeterli tamponlama yoksa boruyu tamamen arapsaçına çevirebilir; gereken tampon boyutu da sistemden sisteme değişir
JMP’in neden sadeceRETolmadığı CONFIG_RETHUNK seçeneği yüzünden.objdumptersine derlemesinde görünen şey,RET’inJMP __x86_return_thunkile değiştirilmiş sonucuhttps://github.com/torvalds/linux/blob/v6.1/arch/x86/include...
https://github.com/torvalds/linux/blob/v6.1/arch/x86/lib/ret...
Fonksiyonun başındaki ve sonundaki NOP komutları ftrace için değil, ASM_CLAC/ASM_STAC makrolarından geliyor. Bu makrolar, X86_FEATURE_SMAP algılandığında çalışma zamanında CLAC/STAC komutlarıyla doldurulacak alan bırakıyor. İki komut da 3 bayt olduğundan NOP sayısıyla aynı
https://github.com/torvalds/linux/blob/v6.1/arch/x86/include...
https://github.com/torvalds/linux/blob/v6.1/arch/x86/include...
https://github.com/torvalds/linux/blob/v6.1/arch/x86/kernel/...
Linux borularına “yavaş” demek, Toyota Corolla’ya “yavaş” demek gibi. Uç kullanım durumları dışında yeterince hızlı
Otomobil yarışı mı yapıyorsun? Üstelik hızın beceriden daha önemli olduğu bir dal mı? O zaman daha hızlı bir araba al. Değilse Corolla’yı sürmeye devam et
Bu yüzden çok küçük verimlilik artışları için bile optimizasyona ciddi zaman ayırmak ekonomik olarak mantıklı
10 yılı aşkın süredir boruları çeşitli amaçlarla kullanıyorum ama boru hızına takıldığım hiç olmadı; çoğu zaman darboğaz büyük olasılıkla tar, gzip, find, grep, nc gibi araçlardı. Elbette bu araçlar da yaptıkları işe kıyasla oldukça hızlı sayılır
FFMpeg, Non-Free SDK’yı kaynak koduna dahil edemiyor; saf RGBA’yı dosyaya yazmak ise akıl almaz derecede pratik dışı. Bu yüzden boru tek yol ve yüksek iş hacimli borulara ihtiyaç duymak için meşru bir neden var
https://www.toyota.com/grcorolla/
Bu araçların mühendisliği ve performansı çok etkileyici; ayrıca başta hedeflenen GR Yaris’i ABD pazarına getirmeyi zorlaştıran kuralları aşmaya yönelik bir hack gibi de duruyorlar. HN kitlesinin hafife almayacağı kadar mühendislik/performans/hack/pazar bağlamı olduğunu düşünüyorum. Üstelik şirketin başkanı da hâlâ bizzat sürüp kullanıyor
Yazının ana noktasından bağımsız olarak, modern CPU’larda
rep movsben hızlı vektörleştirilmiş sürüm kadar hızlıdır. Çünkü CPU’nun bunu hızlandırdığını biliyoruz.Çekirdek işlevi adı
copy_user_enhanced_fast_stringda bunu ima ediyor. İlgili CPU özellikleri ERMS (Enhanced Repeat Move String, belirli bir uzunluğun üzerinderep movsb’yi hızlandırır) ve FSRM’dir (Fast Short Repeat Move String, kısa kopyalamaları da hızlandırır).rep movsbbelirli bir eşik değere kadar hızlıdır, ama sonrasında normal store veya non-temporal store daha hızlıdır.Tüm eşik değerleri https://codebrowser.dev/glibc/glibc/sysdeps/x86_64/multiarch... adresinde açıklanıyor.
Üstelik bu değerler sabit de değil; Noah Goldstein hâlâ her yıl güncelliyor.
memcpyiçin, basit C döngüsü sürümünü silebileceğimiz kadarrep movsbverep stosbhızlansın diye hâlâ bekliyorum.memcpy’yi inline etmesi gibi, değişken uzunluklumemcpy()’yi de ne zaman inline edeceğini merak ediyorum.AVX512 hakkında yazıda görmediğim bir nokta var:
xsave/xrstorek yükünün yanı sıra AVX512 çok güç tüketir ve CPU frekans ölçeklemesini tetikler. Ayrıntılar ve bunun ne kadar incelikli hâle gelebileceği için [1] ve [2]’ye bakılabilir.[1] https://www.intel.com/content/dam/www/central-libraries/us/e...
[2] https://www.intel.com/content/www/us/en/developer/articles/t...
Süreçler arası iletişimin neredeyse her biçimi “yavaş”tır. Güvenlik için performans maliyeti ödemeyi seçmiş oluyoruz.
Özellikle en hızlı süreçler arası iletişimin uzun bir fonksiyon çağrısından daha yavaş olması için pek neden yok.
Yine Hacker News’in hug of death etkisine maruz kalıyorum. WordPress sayfa önbelleklemesi sayesinde geçen seferden daha iyi, ama yine de sayfanın yüklenmesi birkaç saniye sürebilir; anlayışınız için teşekkürler.
splice’ın aslında neden bu kadar yavaş olması gerektiğini pek anlayamamıştım.vmsplice’dan daha yavaş olmasının nedeni olarak buffer ayırma ve skaler komut kullanımı gösterilmiş, ama bunun neden gerekli olduğunu bilmiyorum.Neden
splicebasitçevmsplicegibi yeniden uygulanamıyor? Elbette iyi bir nedeni vardır, ama sanırım kaçırıyorum.vmspliceher tür dosya tanımlayıcısında çalışmaz.io_uring kullanan bir sürümü görmek de ilginç olurdu. Çekirdek ve buffer’ı önceden paylaşarak bazı kopyalamalardan kaçınmak, ayrıca sistem çağrısı ek yükünden de kaçınmak mümkün olabilir. Ancak burada ikincisi neredeyse ihmal edilebilir görünüyor.
Yüklenmesi yaklaşık 20 saniye süren bir blog için oldukça cesur bir iddia.
Yazının kendisi harika görünüyor ve içeride neler olup bittiğine dair öğrenilecek çok şey var.