1 puan yazan GN⁺ 2025-06-28 | 1 yorum | WhatsApp'ta paylaş
  • Docker ile Rust ile yapılmış bir web sitesini tekrar tekrar derlerken derleme süresi sorunu yaşanıyor
  • Varsayılan Docker ayarlarında her seferinde tüm bağımlılıklar baştan derleniyor ve bu işlem 4 dakikadan uzun sürüyor
  • cargo-chef ve önbellekleme araçları kullanılsa da son ikili dosyanın derlenmesi hâlâ çok zaman alıyor
  • Profilleme sonucunda sürenin büyük kısmının LTO (link time optimization) ve LLVM modül optimizasyonuna harcandığı görülüyor
  • Optimizasyon seçenekleri, debug bilgisi ve LTO ayarları değiştirilerek kısmi iyileştirme yapılabilse de son ikili derlemesinin yine de en az 50 saniye sürdüğü doğrulanıyor

Sorunun ortaya konması ve arka plan

  • Rust ile yapılmış kişisel web sitesinde her değişiklikten sonra statik bağlı bir ikili dosya derleyip sunucuya kopyalama ve yeniden başlatma işi sürekli tekrarlanıyor
  • Docker veya Kubernetes gibi konteyner tabanlı dağıtıma geçmek istense de, Rust’ın Docker içindeki derleme hızı büyük bir sorun olarak ortaya çıkıyor
  • Docker içinde küçük kod değişikliklerinde bile her şeyi sıfırdan yeniden derlemek gerektiği için verimsiz bir durum oluşuyor

Docker’da Rust derleme – temel yaklaşım

  • Yaygın Dockerfile yaklaşımı, tüm bağımlılıkları ve kaynak kodu kopyaladıktan sonra cargo build çalıştırmak şeklinde
  • Bu durumda önbelleklemenin avantajı kayboluyor ve tam yeniden derleme sürekli tekrarlanıyor
  • Yazarın web sitesi özelinde tam derleme yaklaşık 4 dakika sürüyor; buna bağımlılık indirmeleri için ek süre de ekleniyor

Docker derleme önbelleğini iyileştirme – cargo-chef

  • cargo-chef aracı kullanılarak yalnızca bağımlılıklar ayrı bir katmanda önceden önbelleğe alınabiliyor
  • Böylece kod değiştiğinde bağımlılık derlemeleri yeniden kullanılarak derleme hızında iyileşme bekleniyor
  • Gerçek kullanımda toplam sürenin yalnızca %25’inin bağımlılık derlemesine gittiği, nihai web servisi ikilisinin derlenmesinin ise hâlâ ciddi zaman aldığı görülüyor (2 dakika 50 saniye ila 3 dakika)
  • Başlıca bağımlılıklar (axum, reqwest, tokio-postgres vb.) ve yaklaşık 7.000 satırlık özel koda rağmen rustc’nin tek bir çalıştırmasının 3 dakika sürmesi dikkat çekiyor

rustc derleme süresi analizi: cargo --timings

  • cargo --timings ile her crate’in (derleme biriminin) derleme süresi görülebiliyor
  • Sonuçta toplam sürenin büyük kısmını nihai ikili derlemesinin oluşturduğu doğrulanıyor
  • Bu yöntem daha ayrıntılı neden analizi için yardımcı olsa da, derleyicinin iç işleyişini somut biçimde anlamada sınırlı kalıyor

rustc’nin kendi profillemesi (-Zself-profile) kullanımı

  • rustc’nin yerleşik profilleme özelliği -Zself-profile bayrağıyla etkinleştirilerek ayrıntılı çalışma süreleri ölçülüyor
  • Bunun için ortam değişkenleri üzerinden profilleme açılıyor
  • Sonuçlar summarize aracıyla incelendiğinde toplam sürenin %60’ından fazlasını LLVM LTO (link time optimization) ve LLVM modül kod üretiminin aldığı görülüyor
  • Flamegraph görselleştirmesi de toplam sürenin %80’inin codegen_module_perform_lto aşamasında harcandığını gösteriyor

LTO (link time optimization) ve derleme optimizasyon seçenekleri

  • Rust derlemesi varsayılan olarak codegen unit’lere bölünüyor, ardından LTO ile tüm program optimizasyonu nispeten geç bir aşamada uygulanıyor
  • LTO için off, thin, fat gibi farklı seçenekler bulunuyor; bunların her biri performansı ve nihai çıktıyı etkiliyor
  • Yazarın projesinde Cargo.toml içinde LTO thin, debug sembolleri ise full olarak ayarlanmış durumda
  • Farklı LTO/debug sembolü kombinasyonları test edildiğinde:
    • full debug sembollerinin derleme süresini artırdığı ve fat LTO’nun derlemeyi yaklaşık 4 kat yavaşlattığı görülüyor
    • LTO ve debug sembolleri kaldırıldığında bile derleme süresi en az 50 saniye oluyor

Ek optimizasyonlar ve notlar

  • Yaklaşık 50 saniye, gerçek servis yükü neredeyse olmayan kişisel site için büyük bir sorun olmasa da, teknik merak nedeniyle ek analizler yapılıyor
  • Artımlı derleme (incremental compilation) Docker ile iyi kullanılırsa daha hızlı derlemeler mümkün olabilir; ancak bunun için temiz bir derleme ortamı ile Docker önbelleğinin birlikte ele alınması gerekiyor

LLVM aşamasının ayrıntılı profillemesi

  • LTO ve debug sembolleri kaldırıldıktan sonra bile sürenin yaklaşık %70’i LLVM_module_optimize aşamasında harcanıyor
  • release profilinde opt-level varsayılanının (3) getirdiği optimizasyon maliyetinin yüksek olduğu fark edilerek yalnızca ikili için opt-level düşürme yöntemi test ediliyor
  • Çeşitli optimizasyon kombinasyonları sonucunda optimizasyon kapalıyken (opt-level=0) sürenin yaklaşık 15 saniye, optimizasyon açıkken (1~3) ise yaklaşık 50 saniye olduğu görülüyor

LLVM izleme olaylarının derin analizi

  • rustc’nin ek bayrakları (-Z time-llvm-passes, -Z llvm-time-trace) ile LLVM aşamalarının çalışma süreleri ayrıntılı biçimde izlenebiliyor
  • -Z time-llvm-passes çok büyük çıktı ürettiği için çoğu zaman Docker’ın log sınırını aşıyor; bu yüzden log ayarlarını değiştirmek gerekebiliyor
  • Loglar dosyaya kaydedilip incelendiğinde her LLVM optimizasyon geçişinin çalışma süresi ayrı ayrı görülebiliyor
  • -Z llvm-time-trace seçeneği, chrome tracing biçiminde devasa JSON çıktısı üretiyor; dosya çok büyüdüğü için sıradan metin düzenleme/analiz araçlarıyla işlemek zorlaşıyor
  • Bu çıktı satır bazında bölünerek (jsonl) CLI veya betik ortamında analiz edilebiliyor

Temel içgörüler ve sonuç

  • Karmaşık Rust projeleri Docker ile derlenirken darboğaz çoğunlukla nihai ikili derlemesi ve buna bağlı LLVM optimizasyon aşamalarında ortaya çıkıyor
  • LTO, debug sembolleri ve opt-level ayarlanırken derleme süresi ile ikili boyutu arasında net bir ödünleşim bulunuyor
  • Optimizasyon ayarları agresif biçimde değiştirilirse derleme süresi ciddi ölçüde kısaltılabiliyor, ancak optimizasyon kapatıldığında performans düşebilir
  • Büyük crate bağımlılıkları olan ve üretim ortamında derleme verimliliğinin önemli olduğu projelerde, profillemeyi aktif kullanarak ayrıntılı darboğazları somut şekilde tespit etmek iyi bir strateji
  • Rust derleme hattı tasarlanırken LTO, opt-level, debug sembolleri ve önbellek stratejilerinin ince ayarlanmış bir kombinasyonla ele alınması gerekiyor

1 yorum

 
GN⁺ 2025-06-28
Hacker News görüşleri
  • Rust projeleri bazen dışarıdan küçük görünse de ilginçtir. Birincisi, bağımlılıklar kod tabanının gerçek boyutuyla bağlantılı değildir. C++'ta büyük proje bağımlılıkları sık sık vendoring ile içeri alınır ya da hiç kullanılmaz; bu yüzden 400 bin satırlık kodda yavaş çok şey varsa, "kod çok, yavaş olması normal" diye düşünebilirsiniz. İkincisi, çok daha sorunlu kısım makrolardır. 10 satırı, 100 satırı tekrar tekrar genişleten makrolar, 10 bin satırlık bir projeyi çok kısa sürede milyon satıra çıkarabilir. Üçüncüsü generics'tir. Her generic instance oluşturma CPU kaynağı tüketir. Yine de biraz savunmak gerekirse, bu özellikler sayesinde C'de 100 bin satır, C++'ta 25 bin satır sürecek şeyler Rust'ta birkaç bin satıra inebilir. Ama bu özelliklerin aşırı kullanılması yüzünden ekosistemin yavaş görünmesi de bir gerçek. Örneğin şirketimizde async-graphql kullanıyoruz; kütüphanenin kendisi harika ama procedural macro bağımlılığı çok yüksek. Performansla ilgili sorunlar yıllardır açık ve her yeni veri tipi eklediğimizde derleyicinin belirgin biçimde yavaşladığını hissediyoruz

    • Orijinal kodun zaten basit olduğu yerlerin, örneğin küçük C yardımcı araçlarının, neden sık sık Rust'ta yeniden yazıldığını merak ediyorum. 100 bin satırlık büyük C programlarının Rust'a taşınmasından daha sık gördüğüm şey çok küçük ölçekli kodlar. Küçük programların derleme hızında Rust ile C nasıl karşılaştırılıyor, bunu merak ediyorum. Program boyutunu değil derleme hızını soruyorum. Ayrıca son ölçümlerime göre Rust derleyici toolchain'inin boyutu kullandığım GCC'nin yaklaşık 2 katı. 1. Bu kadar küçük programlarda hangi dil olursa olsun gizli bellek güvenliği sorunları olma ihtimali düşüktür ve boyut küçük olduğu için denetim de kolaydır. 100 bin satırlık C programıyla aynı durum değil
    • Yeni bir tip tanımladığınız her sefer derleyicinin yavaşladığını doğrudan hissedebiliyorsunuz. Bildiğim kadarıyla derleyici performansı tiplerin “derinliğine” bağlı olarak üstel biçimde yavaşlıyor. GraphQL gibi yerlerde iç içe tip çok olduğu için bu sorun özellikle ciddi
    • Makrolar onlarca ya da yüzlerce satıra genişlediğinde kod tabanının geometrik olarak büyüyebilmesi sorununa karşı yakın zamanda analiz aracı desteği eklendi. İlgili kaynak: https://nnethercote.github.io/2025/06/26/how-much-code-does-that-proc-macro-generate.html
  • Ryan Fleury, Epic RAD Debugger'ı C ile 278 bin satırlık unity build tarzında yaptı (tüm kod tek bir dosyada, tek bir derleme birimi olarak) ve Windows'ta clean compile sadece 1,5 saniye sürüyor. Sadece bu örnek bile derlemenin ne kadar hızlı olabileceğini gösteriyor; Rust veya Swift'te neden benzeri yapılamıyor merak ediyorum

    • Derleme zamanında derleyicinin yaptığı iş arttıkça derleme süresi uzar. Go, büyük kod tabanlarında bile 1 saniyenin altında build sürelerine ulaşabiliyor. Çünkü build sırasında gereken işleri minimumda tutan basit bir modül sistemi ve tip sistemi var; ayrıca çoğu işi çalışma zamanındaki GC'ye bırakıyor. Buna karşılık makrolar, karmaşık tip sistemleri ve yüksek düzeyde sağlamlık istendiğinde build süresi kaçınılmaz olarak uzuyor
    • Rust'ta da build birimi crate'in tamamıdır ve derleyici bunu LLVM IR düzeyinde uygun boyutlara böler. Tekrarlanan işleri ve incremental build dengesini de kendi ayarlar. Kaynak kod satırı bazında Rust'ın C++'tan daha hızlı build aldığı durumlar sık görülür. Ancak Rust projelerinde bağımlılıkların da tamamı derlenir
    • Rust ve Swift'in C derleyicilerinden daha yavaş olmasının nedeni, dillerin çok daha fazla analiz gerektirmesidir. Örneğin Rust'ın borrow checker'ı bedava gelmiyor. Sadece derleme zamanı denetimleri bile ciddi kaynak tüketiyor. C'nin hızlı olmasının nedeni, temel sözdiziminin ötesinde neredeyse hiçbir şeyi denetlememesi. Hatta C, foo(char*) için foo(int) çağırmak gibi tuhaf eşleşmeleri bile kontrol etmez
    • 2000'lerde on binlerce satırlık C++ projeleri derliyordum; eski bilgisayarlarda bile build 1 saniyenin altında bitiyordu. Buna karşılık sadece Boost kullanan bir HELLO WORLD birkaç saniye sürüyordu. Sonuçta build hızı sadece dil veya derleyiciye değil, kodun yapısına ve kullanılan özelliklere de büyük ölçüde bağlı. C makrolarıyla DOOM bile yapılabilir ama muhtemelen hızlı olmaz. Tersine, Rust da hızlı build alacak şekilde yapılandırılabilir
    • C ve Go gibi hızlı derlemeyi hedefleyen dillerin hızlı olması pek şaşırtıcı değil. Asıl zor olan, Rust'ın semantiğini hızlı derlemektir. Bu konu Rust'ın resmi SSS'sinde de var
  • Go'nun optimizasyondan çok derleme hızını öncelemiş olmasına gerçekten seviniyorum. Sunucu, ağ ve glue code işlerinde derlemenin gerçekten hızlı olması her şeyden önemli. Bir miktar tip güvenliği de istiyorum ama gevşek prototiplemeyi engellemeyecek düzeyde. GC olması da kullanışlı. Bence Google, çok büyük ölçekli geliştirme deneyiminden sonra basit tipler, GC ve aşırı hızlı derlemenin; çalışma hızı veya anlamsal mükemmellikten çok daha önemli olduğu sonucuna vardı. Go ile yapılmış büyük ağ ve altyapı yazılımlarına bakınca bu tercih tam isabet görünüyor. Elbette GC'nin kabul edilemez olduğu ortamlarda ya da kusursuz doğruluğun daha önemli olduğu yerlerde Go kullanılmayabilir ama benim çalışma ortamımda Go'nun tercihleri en uygunu

    • Ben de Go'yu seviyorum ama bu dilin kurumsal Google'ın muazzam kolektif zekâsının ürünü olduğunu düşünmüyorum. Google deneyimi gerçekten daha fazla yansısaydı, örneğin null pointer exception'ları statik olarak ortadan kaldıran özellikler eklenirdi. Daha çok bazı Google geliştiricilerinin istedikleri dili yazmış olmaları gibi geliyor
    • Hızlı derleme, makul bir tip sistemi ve GC gibi Go'nun güçlü yanları var ama tasarım alanında benzer bir yeri Java zaten kapmıştı. Go'nun ortaya çıkmasında asıl etkenin yaratma isteği olduğunu düşünüyorum ve sonuçta orijinal hedef kitlesinden (sunucu tarafı C/C++/Java) çok betik dili (Python/Ruby/JS) kullanıcıları tarafından daha fazla benimsendi gibi duruyor. Betik dili kullanıcıları kolay ve hızlı bir tip sistemi istiyordu; Java ise fazla eski ve sıkıcıydı. Java'nın sunucu/konferans/kütüphane alanında zaten boş alanı kalmamıştı
    • Google geliştiricilerinin, C++ projesinin derlenmesini beklerken Go'yu tasarladığı yönünde bir hikâye de var
    • “obnoxious type” ile ne kastedildiğini sormak isterim. Tipler ya veriyi doğru ifade eder ya etmez; pratikte hangi dilde olursa olsun tip denetleyiciyi zorla susturmanın bir yolu bulunabilir
    • Go, tasarım amacıyla gerçek kullanım alanı birbirine çok iyi uyan bir dil. En büyük risk, paralel işleme ve mutable durumun channel'lar üzerinden paylaşılması; burada ince veya kırılgan hatalar çıkabiliyor. Genelde kullanıcıların çoğu zaten bu deseni kullanmıyor. Ben Rust kullanıyorum çünkü işim, zaten yavaş olan algoritmaları yavaş donanımda mümkün olduğunca zorlayarak çalıştırmak. Bu nedenle büyük ölçekli paralelleştirme çok hassas biçimde kısıtlı bir problem
  • Tek bir statik ikili kurulumunun konteyner yönetiminden daha basit olduğu iddiasını anlayamıyorum

    • Sanırım docker'ın gerçekte ne yaptığını net biçimde anlamamış. Örneğin "docker image ile dağıtırsan her seferinde her şeyi baştan build edersin" denmiş ama kurum içi build/deploy ortamlarında böyle olmak zorunda değil. Kişisel kullanımda da geliştirme kolaylığını koruyup, yerelde build ettiğin dosyayı doğrudan konteynere koyabilirsin. Sadece build ortamından kalan yol izlerine dikkat etmen yeterli. CI/CD veya ekip projelerinde önemli olan her yerde sıfırdan build üretilebilmesini garanti etmektir; kişisel işlerde buna gerek olmayabilir
    • Orijinal metinde hedef basitleştirme değil, modernleştirme. Yani "son 10 yılda çoğu yazılım konteyner dağıtımını standart aldı, ben de web sitemi docker, kubernetes gibi konteynerlerle dağıtacağım" deniyor. Konteynerlerin süreç yalıtımı, güvenlik, standartlaştırılmış loglama, yatay ölçeklenebilirlik gibi çeşitli avantajları var
  • Dizüstümde (Mac M4 Pro) Deno'nun tamamını derlemek 2 dakika sürüyor; bu büyük bir Rust projesi. Komut bazında bakınca debug yaklaşık 1 dakika 54 saniye, release ise yaklaşık 8 dakika 17 saniye sürüyor. Bunlar incremental compilation olmadan ölçülmüş değerler. Gerçi dağıtım build'leri zaten CI/CD sisteminde çalıştığı için bunları doğrudan beklemek zorunda değilim

    • M1 Max'te 6 dakika, M1 Air'de yaklaşık 11 dakika sürdüğünü söyleyen bir ilgili yazı var
  • Cranelift'ten neden hiç bahsedilmiyor? Bana kalırsa Rust ile oyun geliştirirken derleme süreleri o kadar uzundu ki neredeyse vazgeçecektim. Araştırınca LLVM'in optimizasyon seviyesinden bağımsız olarak yavaş olduğunu gördüm. Jai dili geliştiricileri bunu hep söylüyordu. Cranelift ile build süresinin 16 saniyeden 4 saniyeye düştüğünü yaşadım. Cranelift ekibine hayran kaldım!

    • Son Bevy game jam'de Dioxus topluluğundan çıkan 'subsecond' adlı bir araç kullandık; adı gibi sistem hot reload süresini 1 saniyenin altına indirebiliyor ve UI prototiplemede çok yardımcı oldu. https://github.com/TheBevyFlock/bevy_simple_subsecond_system
    • Bildiğim kadarıyla zig ekibi de LLVM olmadan kendi derleyicisini/backend'ini yapıp build sürelerini ciddi biçimde hızlandırmaya çalışıyor
    • Eskiden Cranelift'in macOS aarch64 desteği yok sanıyordum ama yakın zamanda desteklediğini öğrendim
    • 16 saniyelik build süresi yüzünden Rust'tan vazgeçmenin eşiğine gelmek biraz abartı değil mi?
  • Rust'ın yavaş olduğunu düşünmüyorum. Benzer seviye dillerle kıyaslayınca yeterince hızlı; 15 dakikaya varan C++/Scala derlemelerine göre çok daha iyi

    • Ben de katılıyorum. Rust build'leri bana hiç özellikle rahatsız edici gelmedi. Muhtemelen erken dönemdeki kötü ün hâlâ sürüyor ve bu algı oradan geliyor
    • Derleme sırasındaki bellek kullanımı C/C++'a göre çok yüksek. YouTube demosu için kullandığım VM'de büyük bir Rust projesini derlemek istiyorsam 8 GB'tan fazla gerekiyor. C/C++'ta böyle bir kaygım olmuyor
    • C++ template'lerinin Turing-complete olduğu düşünülürse, gerçek kod stilini hesaba katmadan sadece build süresini karşılaştırmak pek anlamlı değil
  • Eski bir C++ geliştiricisi olarak Rust build'lerinin yavaş olduğu iddiasını pek anlayamıyorum

    • Zaten Rust'ın C++ geliştiricilerini hedeflediğinin söylenmesinin sebebi bu. C++ geçmişi olan geliştiriciler, araçların rahatsız ediciliğine dayanmayı öğrenmiş bir Stockholm sendromuna sahip
    • C++'tan hızlı olsa bile mutlak anlamda yavaş olabilir. C++ build'lerinin ne kadar kötü şöhretli olduğunu herkes biliyor. Rust, yapısal dil sorunları taşımadığı için beklenti seviyesi daha yüksek oluyor galiba
    • Bana göre bu, sürekli yeni özellik eklenip gerçek kullanıcı geri bildiriminin yeterince dinlenmediği klasik bir örnek
    • C'nin derleme aşamaları az ve basit olduğu için hızlıydı; ama C++ template kullanımı yüzünden neredeyse bütün encapsulation işlerini yıktı diye hissediyorum. Tek bir template header'ını değiştirmek bile projenin %98'ini etkilemiş gibi oluyor
  • Incremental compilation gerçekten çok güçlü. İlk build'den sonra incremental cache snapshot'ını sabitleyip değişiklik yoksa aynı hâliyle hızlı build/deploy için kullanabiliyorsunuz. Docker ile de uyumu iyi. Derleyici sürümü veya büyük web sitesi güncellemeleri dışında image build katmanlarına dokunulmuyor. Sadece kod değiştiğinde ilgili katmanın yeniden build edilmemesini sağlayabilirseniz çok verimli oluyor

    • Projemdeki incremental artifact'ler 150 GB'ı geçti. Docker image'ını bu kadar büyük kullanınca pratikte çok ciddi sorunlar yaşamıştım
  • Ana sayfamın build süresi 73 ms. Static site generator 17 ms'de yeniden derliyor. Generator'ün fiilen çalışması da sadece 56 ms sürüyor. Zig build log çıktısını ekledim

    • C/C++ konusunda Rust öven yorumlar, Rust konusunda da Zig öven yorumlar hep çıkıyor gibi. (Meğer bu yorumun yazarı zig'in ana geliştiricisiymiş.) Dil evangelizminin topluluklara zarar verdiğini; pratikte sadece tepki doğurduğunu ve yeni kullanıcı çekmediğini düşünüyorum. Bir dili gerçekten seviyorsanız bu evangelizm kültürünü frenlemek daha faydalı olur
    • Tek bir derleme süresi metriği vermek yerine, asıl yazının konusuyla doğrudan ilgili bir tartışma veya yorum olsaydı daha iyi olurdu
    • Benim Rust web sitemde de (react benzeri framework ve fiilen bir web sunucusu dâhil) cargo watch ile incremental build yaklaşık 1,25 saniye sürüyor. subsecond[0] gibi araçlarla incremental linking ve hotpatch de kullanırsanız daha da hızlanıyor. Zig kadar hızlı değil ama oldukça yakın. Eğer yukarıdaki 331 ms clean build ise, benim web sitemin 12 saniyelik clean build'inden çok daha hızlı. [0]: https://news.ycombinator.com/item?id=44369642
    • @AndyKelley'ye özellikle sormak isterim: Sizce zig'in aşırı hızlı derlenip Rust ve Swift'in sürekli yavaş kalmasının belirleyici nedeni nedir?
    • Zig bellek güvenliği garantisi vermiyor, değil mi?