2 puan yazan GN⁺ 2023-11-11 | 1 yorum | WhatsApp'ta paylaş
  • Rust derleyicisinin paralelleştirmesi şimdiye kadar ağırlıklı olarak Cargo ve LLVM arka ucuna dayanıyordu; artık buna ön uçta paralel yürütme de eklenerek derlemenin son aşamalarındaki darboğazlar azaltılabiliyor
  • nightly’de deneysel özellik -Z threads=8 ile açılabiliyor; varsayılan değer hâlâ tek iş parçacıklı mod olduğu için açıkça ayar yapılmadıkça hız artışı sağlanmıyor
  • Yeni ön uç Rayon tabanlı olarak ince taneli derleme işlerini bölüyor; örnek ölçümde ön uç süresi 10,2 saniyeden 5,9 saniyeye düştü
  • Gerçek kod ölçümlerinde derleme süresi en fazla %50 azaldı, ancak kod özelliklerine ve derleme ayarlarına göre büyük değişkenlik gösteriyor; bellek kullanımı ise en fazla %35 artabiliyor
  • Özellik hâlâ deneysel aşamada; -Z threads kararlılığı ve stable’da çok iş parçacıklı varsayılan çalıştırma hedefi 2024 olarak izleniyor

Rust derlemede paralelleştirmenin yeni ekseni

  • Rust derleyici ön ucu artık paralel yürütmeyle derleme süresini azaltabiliyor
  • nightly derleyicide -Z threads=8 seçeneği verilerek çok iş parçacıklı ön uç denenebiliyor
  • Bu özellik hâlâ deneysel; stable derleyiciye dahil edilmesi için hedef zaman 2024

Mevcut optimizasyonlar ve kalan darboğazlar

  • Compiler Performance Working Group, yıllardır Rust derleyici performansını iyileştiriyor
    • 2023’ün ilk 10 ayında, performans ölçüm araçlarına göre ortalama derleme süresi %13 azaldı
    • Tepe bellek kullanımı %15 düştü
    • İkili dosya boyutu %7 küçüldü
  • Derleyici zaten büyük ölçüde optimize edilmiş durumda olduğundan, kalan büyük iyileştirme alanı daha çok paralelliği artırmaya yakın

Cargo ve arka uç paralelleştirmesinin sınırları

  • Cargo, Rust programı derlerken birden fazla rustc süreci çalıştırarak crate’leri paralel derler
  • Bu paralelleştirme -j1 bayrağıyla kapatıldığında büyük Rust programlarının derleme süresi ciddi biçimde artar
  • Cargo’nun --timings bayrağı, crate derleme zaman çizelgesi grafiği oluşturur
  • ripgrep’in 28 sanal çekirdekli bir makinede derlendiği örnekte 60 süreç satırı görülür
    • Çoğu rustc, bir kısmı ise build script’tir
    • İlk 20 süreçte crate’ler arası bağımlılık olmadığı için aynı anda başlayabilirler
    • Derlemenin sonlarına doğru crate bağımlılıkları arttıkça paralellik azalır
  • pipelined compilation ile bağımlı crate derlemeleri bir ölçüde örtüştürülebilse de büyük Rust programlarında derlemenin son aşamalarında paralel yürütme çok daha az olur

Ön uç ve arka ucun üstlendiği işler

  • Rust derleyicisi genel olarak ön uç ve arka uç olarak ikiye ayrılır
  • Ön uç ayrıştırma, tip denetimi, borrow checking gibi işlemleri gerçekleştirir
  • Mevcut ön uç paralel yürütmeyi kullanamıyordu
  • Arka uç kod üretiminden sorumludur; kodu “codegen units” birimleri halinde ürettikten sonra LLVM bunları paralel işler
  • Release derlemelerinde varsayılan codegen unit sayısı 16’dır; bu nedenle örnek profilde 16 LLVM iş parçacığı görünür

Mevcut arka uç profilinde ortaya çıkan darboğaz

  • Cargo’nun son crate’i release modda derlenirken Samply ile yapılan örnek ölçümde ön uç 10,2 saniye sürdü
  • Arka uç 6,2 saniye sürdü; LLVM iş parçacıkları bunun 5,9 saniyesi boyunca çalıştı
  • LLVM tabanlı paralel kod üretimi etkili olsa da 28 çekirdekli makinede bile 16 LLVM iş parçacığının tamamı aynı anda çalışmadı
  • main thread, MIR’i LLVM IR’ye dönüştürme işini seri olarak yaptığı için codegen thread başlangıçlarında basamaklı bir görünüm oluştu
  • Tamamen seri çalışan ön uç, en büyük iyileştirme noktası olarak kalmıştı

Yeni paralel ön ucun uygulama biçimi

  • Yeni ön uç, ince taneli paralel derleme işleri yürütmek için Rayon kullanır
  • Birçok veri yapısı mutex ve read-write lock ile senkronize edilir; gereken yerlerde atomic tipler kullanılır
  • Birçok ön uç işi paralelleştirilmiş olsa da değişiklikler nispeten az sayıdaki temel noktada yoğunlaşır
  • Ön uç kodunun büyük bölümünü değiştirmeye gerek kalmadı

8 iş parçacıklı ayarın ölçüm sonuçları

  • Paralel ön uç etkinleştirilip 8 iş parçacığı kullanılan aynı örnekte, ön uç çalışma süresi 10,2 saniyeden 5,9 saniyeye düştü
  • Arka uç çalışma süresi 6,2 saniyeden 5,3 saniyeye, LLVM iş parçacığı çalışma süresi ise 5,9 saniyeden 4,9 saniyeye azaldı
  • Ön uçta rustc olarak gösterilen ek 7 iş parçacığı çalışır
  • İş parçacığı kullanımı dengeli değildir ve 8 iş parçacığının tamamında boşta kalınan aralıklar bulunduğundan ek iyileştirme alanı vardır
  • 8 LLVM iş parçacığının aynı anda başlamasının nedeni, 8 rustc iş parçacığının 8 codegen unit için LLVM IR’yi paralel üretmesidir
  • Ön uç iş parçacığı sayısı 16’ya çıkarılırsa basamaklı görünüm tamamen kaybolur, ancak bu vakadaki nihai çalışma süresi neredeyse değişmez

Süreçler arası paralelleştirme ile süreç içi paralelleştirmenin birleşimi

  • Rust derleme uzun süredir Cargo’nun süreçler arası paralelleştirmesinden ve arka ucun süreç içi paralelleştirmesinden yararlanıyor
  • Artık ön uç da süreç içi paralelleştirmenin avantajlarından yararlanabiliyor
  • Birden fazla rustc süreci aynı anda çalışırken ve her süreç birden fazla iş parçacığı oluştururken, jobserver protocol iş parçacığı sayısını sınırlar
  • Süreçler arası paralellik yüksek olduğunda süreç içi paralellik buna göre azalır ve toplam iş parçacığı sayısı çekirdek sayısını aşmaz

Kullanım yöntemi

  • nightly derleyiciye paralel ön uç dahil edilmiş durumda
  • Varsayılan değer tek iş parçacıklı mod olduğu için olduğu haliyle derleme süresi azalmaz
  • Çok iş parçacıklı mod -Z threads seçeneğiyle açıkça açılmalıdır
RUSTFLAGS="-Z threads=8" cargo build --release
  • Bir veya daha fazla projede config.toml ile ayarlamak için şunu ekleyin
[build]
rustflags = ["-Z", "threads=8"]
  • Tek iş parçacıklı modun varsayılan olmasının nedeni temkinli dağıtımdır
    • Paralel ön uçta çok sayıda yeni kod bulunur
    • Tek iş parçacıklı mod yeni kodun büyük bölümünü çalıştırır, ancak deadlock gibi threading hatası olasılıklarını dışarıda bırakır
    • Paralel programları doğru yazmak Rust’ta bile seri programlara göre daha zordur
    • Bu nedenle paralel ön uç bir süre beta veya stable sürümlere dahil edilmeyecek

Performans ve bellek etkisi

  • Tek iş parçacıklı modda paralel ön uç, mevcut seri ön uca göre genellikle %0~%2 daha yavaştır
  • -Z threads=8 çok iş parçacıklı modda gerçek kod ölçümlerinde derleme süresi en fazla %50 azalabilir
  • Performans etkisi kod özelliklerine ve derleme ayarlarına göre büyük ölçüde değişir
    • Geliştirme derlemeleri release derlemelerine göre daha büyük iyileştirme görebilir
    • Çünkü release derlemeleri genellikle arka uç optimizasyonlarına daha fazla zaman harcar
    • Zaten hızlı derlenen bazı küçük programlarda çok iş parçacıklı mod, tek iş parçacıklı moddan daha yavaş olabilir
  • Önerilen değer 8 iş parçacığıdır
    • En çok test edilen ayardır ve iyi sonuç verdiği bilinmektedir
    • 8’den düşük değerlerin getirisi daha küçüktür, ancak 8’den az çekirdekli donanımlar için uygundur
    • 8’den yüksek değerlerde getiriler azalır ve performans daha da kötüleşebilir
  • 1’den 8 iş parçacığına çıkıldığında bile iyileştirmenin %50 düzeyinde kalmasının nedeni, ön ucun toplam derleme süresinin yalnızca bir bölümü olması ve arka ucun zaten paralelleştirilmiş bulunmasıdır
  • Çok iş parçacıklı modda bellek kullanımı belirgin biçimde artabilir; en fazla %35 artış gözlemlenmiştir

Doğruluk ve geri bildirim

  • Tek iş parçacıklı modun güvenilirliğinin yüksek olması bekleniyor
  • Çok iş parçacıklı modda deadlock dahil bilinen hatalar var
  • Derleme takılırsa bilinen hatalardan biriyle karşılaşmış olmanız mümkündür
  • Hangi ön uç kullanılırsa kullanılsın derleyicinin ürettiği ikili dosya aynı olmalıdır; fark varsa bu hata kabul edilir
  • Sorun varsa önce WG-compiler-parallel etiketli issue’lara bakılabilir; eşleşen mevcut bir issue yoksa yeni issue açılabilir
  • Genel geri bildirim wg-parallel-rustc Zulip channel üzerinden alınabilir; özellikle gerçek koddaki performans etkisiyle ilgileniliyor

2024 stable hedefi

  • Paralel ön ucun performans iyileştirme çalışmaları sürüyor
  • Profilde görüldüğü gibi ön uç iş parçacığı kullanım oranında hâlâ iyileştirme alanı var
  • Çok iş parçacıklı modun kalan hataları da temizleniyor
  • -Z threads seçeneğinin kararlı hale getirilmesi ve stable sürümde paralel ön ucun çok iş parçacıklı varsayılan olarak sunulması için hedef zaman 2024

1 yorum

 
GN⁺ 2023-11-11
Hacker News yorumları
  • Henüz erken aşamada olduğunu biliyorum ama Rust’ın dezavantajının derleme hızı olduğunu düşünüyorum
    Bir Rust monorepo’sunda çalıştığımda en büyük şikâyetim derleme hızıydı; CI/CD maliyetlerini artırıyordu ve cache’i temizlemek gerektiğinde geliştirme süresini de ciddi biçimde yavaşlatıyordu
    Bunun nedeni Cargo değil, bir Docker hatasıydı; yine de bu tür ilerlemeler sevindirici

    • Dramatik bir iyileşme olma ihtimali düşük görünüyor
      Zaten epey optimize edilmiş durumda ve mevcut Rust derleyicisi neredeyse tüm ana akım derleyicilerden daha yüksek paralellik sunuyor
      Rust’ın dil tasarımının kendisi, hızlı derlemeyi hedefleyerek tasarlanmış Go gibi dillere kıyasla derlemeyi daha zor hale getiriyor
    • rust-analyzer, rustc’nin kendisinden bile daha fazla kaynak tüketiyor
      Bu çalışmanın oraya ne kadar doğrudan uygulanacağını bilmiyorum ama orada da büyük bir iyileşme olursa güzel olur
      Modern IDE desteği için kesinlikle gerekli bir parça
    • Buna pek katılmak zor
      Orta ölçekli bir açık kaynak Rust projesinin [1] bakımını yapıyorum; yerelde Rust derleme sürelerini her zaman şaşırtıcı derecede hızlı buluyorum
      MacBook Pro’da debug build’ler birkaç saniye düzeyinde; release build ve CI/CD yavaş ama 2 yıl önce Rust’a başladığımdan beri Rust derlemesi bana çok hızlı geliyor
      Dengelemek gerekirse, asıl işim Java/Kotlin ve Gradle ile; burada gerçekten buzul gibi derleme sürelerinden söz edebilirim
      Açık kaynak Rust projemde bağımlılıkları minimumda tutuyorum, derive[Debug, Clone] gibi şeyler dışında makro kullanmıyorum ve generic kullanımını da çok sınırlı tutuyorum
      Bu projeyi cargo build ile derleyip derleme süresiyle ilgili geri bildirim verirseniz iyi olur
      [1]: https://github.com/Orange-OpenSource/hurl
    • Rust’ta yalnızca küçük projeler yapmış biri olarak kod satırı sayısının aşağı yukarı ne kadar olduğunu merak ediyorum
      Ayrıca projeyi uygun noktalarda birden fazla crate’e bölüp bölmediğinizi de merak ediyorum
    • Gerçekte ne kadar yavaş olduğunu merak ediyorum
      Monorepo build’i kaç dakika sürüyordu?
      Olabildiğince dürüst olabilirsiniz. Çoğunlukla kullandığım derleyici GHC olduğu için kolay kolay şaşırmam
  • Aptalca bir soru olabilir ama backend, frontend’in borrow checking işlemini bitirmesini beklemek zorunda mı? Öyleyse neden?
    Bir şeylerin yanlış olduğu anlamına gelsin diye sormuyorum; borrow checking’in basit bir tutarlılık kontrolünden öte, backend’in dayandığı invariant’ları kurup kurmadığını merak ediyorum
    Örneğin borrow checking hatası çıkarsa atılabilecek spekülatif backend çalışması yapılamamasının bir nedeni var mı merak ediyorum

    • mrustc[0] adında borrow checker’ı olmayan bir Rust derleyicisi var; yani en azından bazı Rust sürümleri borrow checker olmadan da derlenebiliyor
      Ancak en optimize kodu üretmeyebilir
      Bildiğim kadarıyla, meşhur noalias optimizasyonu gibi borrow checking sırasında kurulan bilgileri kullanan optimizasyonlar var. Bu optimizasyonun açılabilmesi birkaç deneme gerektirmişti[1]
      Ayrıca bunun NLL (non-lexical lifetimes) ile ilişkisi de net değil; backend’in ilgileneceği bilgileri kurmak için en azından ilkel bir borrow checker gerekebilir gibi geliyor
      Öte yandan mrustc, NLL özelliği olan Rust sürümlerini de borrow checker olmadan derlediği için, zorunlu olmaktan çok optimizasyon tarafına daha yakın gibi
      [0]: https://github.com/thepowersgang/mrustc
      [1]: https://stackoverflow.com/a/57259339
    • Kesin konuşmak gerekirse, bence mutlaka öyle olması gerekmiyor
  • Farklı makinelerde kullanılan yapılandırma dosyalarına sabit değer yazmak yerine, CPU çekirdeği sayısını kullanmasını sağlamanın bir yolu var mı?

    • Bunun hâlâ deneysel aşamada olduğunu ve yalnızca nightly ile sınırlı olduğunu, yani genel kullanım için ayarlanmadığını hesaba katmak gerek
      Kararlı hale geldiğinde varsayılan değerin çekirdek sayısı olacağını tahmin ediyorum
      Bu işin şu anda nereye kadar geldiğini bilmiyorum ama bir ara Cargo’nun rustc çağrıları arasında jobserver ile koordinasyon sağlamaya çalışıyorlardı; öyle olursa varsayılanı çekirdek sayısı olan Cargo iş sayısını kullanır
      Cargo, negatif değerlerle çekirdek sayısından düşme yöntemini de destekliyor
    • RUSTFLAGS ortam değişkenini kullanabilirsiniz; yazıda da geçiyor:

      $ RUSTFLAGS="-Z threads=8" cargo build --release

    • jobserver protokolünü kullandığından ve Cargo varsayılan olarak çekirdek sayısıyla başlattığından, yeni bayrağı 10000 gibi gerçekçi olmayan büyük bir değere ayarlarsanız kullanımın boşta kalan çekirdek sayısıyla sınırlanacağını düşünüyorum
  • Güzel! Çok uzun zaman önce Rust kullandığımda oyuncak örneklerin bile derlenmesi epey yavaştı; yakın zamanda geri döndüğümde Rust’ın gerçekten iyileştiğini gördüm ve derleme süresini neredeyse hiç düşünmeden mümkün olan her yerde kullanıyorum
    Yine de biraz büyüyen bir projede basit değişikliklerin bile derlenmesi 5 saniyeden fazla sürmeye başlayınca eski anılar canlandı
    Laptop uçak motoru gibi dönmeden önce başka şeyleri toparlayana kadar analyzer’ın çalışmaması için kaydetmeyi ertelemek istediğimi bile fark ettim
    Kişisel olarak en büyük acı noktam bu olduğu için her türlü ilerleme çok sevindirici

  • Güzel! Kütüphane crate ekosisteminin aksine, benim binary crate’lerim varsayılan olarak büyük ve monolitik olma eğilimindeydi
    Şimdi onları birden fazla kütüphane crate’ine bölüyorum
    Bu, derlemenin son aşamasında paralelleşme olmamasının yanı sıra en büyük crate’lerin sıralı işlendiği anlamına geliyor; bu yüzden bu değişiklik çok sevindirici

  • Birkaç yıl Rust’tan yarı uzak durup Python veya TypeScript gibi ortamlarda çalıştıktan sonra, yakın zamanda bir projede tekrar kullandım ve derleme hızı neredeyse anında biten düzeydeydi
    Daha da iyileşmesi her zaman iyi ama zaten oldukça harika durumda
    Bugünlerde ChatGPT gibi bir hile koduyla, birkaç yıl önce takılıp kalabileceğim zor Rust sorunlarının da neredeyse üstesinden gelebiliyorum; bu yüzden Rust tarafının geleceği oldukça iyi görünüyor

    • Benim derleme sürelerim genelde iyi ama GitHub Actions üzerinde çapraz mimarili Docker image’ları oluştururken durum istisna
      O zaman Docker image build’i 60–90 dakika sürüyor ve projenin toplam bağımlılıklarının ne kadar fazla olduğunu fark ediyorsunuz
    • Derleme süresi bağımlılıkların miktarına, boyutuna ve karmaşıklığına çok büyük ölçüde bağlı
  • Derleyiciyi yeniden build etmeden paralel derleyici seçeneğini kapatmanın bir yolu var mı?
    Kullanmaya ihtiyacım yok, zaten codegen units’i de 1’e ayarladım ve debug etmek istemediğim ICE’lere yol açıyor gibi görünüyor
    Varsayılan değerin 1 thread olduğunu biliyorum ama tamamen kapatmak istiyorum

  • “Çok iş parçacıklı modda, deadlock dahil bilinen hatalar var. Derleme takılırsa muhtemelen bunlardan birine denk gelmişsinizdir.”
    O halde -Z threads kullanmadan önce biraz daha beklemeliyim ;)