Gleam’in 3 yıllık performans sorununu Rust arena ile çözmek
(giacomocavalieri.me)- Gleam formatlayıcısının özyinelemeli
Documentyapısında tekrarlanan ayrı ayrı heap tahsislerini arena tabanlı referanslarla değiştirerek 3 yıldır açık kalan performans iyileştirme sorunu çözüldü Box<Self>, arena’da saklanan&Selfile değiştirildi ve Rust’ın ömür denetimi sayesinde arena kaldırıldıktan sonra iç verilerin referanslanmaması garanti altına alındı- Dil anahtar sözcükleri ve virgüller gibi tekrarlanan yüzlerce
Document, yalnızca bir kez tahsis edilip yeniden kullanılarak pretty printer çalışma süresi 13 ms’den 9,8 ms’ye düştü; %24 hızlanma sağlandı - Kaynak okuma ve ayrıştırmayı da içeren toplam
gleam formatçalışma süresi de %13 kısaldı; tepe bellek kullanımı 8,4 MB’den 7,6 MB’ye inerek yaklaşık %10 azaldı - Arena’yı iletmek için fonksiyonların ve çağrı noktalarının geniş ölçekte değiştirilmesi gerektiğinden
+2963/-1032büyüklüğünde manuel çalışma yapıldı; ancak tekrarlayan ayrı tahsisleri azaltma yaklaşımıyla hem hız hem de bellek kullanımı iyileştirilebildi
Özyinelemeli Document yapısında ortaya çıkan tahsis maliyeti
- Gleam’in pretty printer’ı, kodun nasıl yazdırılacağını ve satır sınırı aşıldığında nereden bölüneceğini temsil eden özyinelemeli bir
Documentveri yapısı kullanır - Başlıca varyantlar, yazdırılacak metni taşıyan
String, satır kırılmasından önceki ve sonraki metni tanımlayanBreak, birden fazla belgeyi gruplayanGroup, satır kırıldığında girintiyi artıranNestgibi yapılardan oluşur [1, 2]listesi deString,Break,Nest,Groupbileşimiyle ifade edilir- Geçerli satıra sığıyorsa
[1, 2]gibi tek satırda yazdırılır - Satır sınırını aşarsa belirtilen
Break’lere göre birden çok satıra bölünür, girintilenir ve sondaki virgül de yazdırılır
- Geçerli satıra sığıyorsa
Nestgibi içinde başka birDocumenttaşıyan varyantlar daha önceBox<Self>kullandığından, her iç içe belge için heap üzerinde ayrı tahsis yapılması gerekiyordu; bu işlem çalışma süresinin önemli bir bölümünü oluşturabiliyordu- Gleam pretty printer’ının temel algoritması Strictly Pretty makalesinde görülebilir
Box’ı arena referansıyla değiştirmek
- Mevcut
Nest(Box<Self>),Nest(&'doc Self)olarak değiştirilerek yapının arena’da saklanan başka birDocument’ı referanslaması sağlandı - Referans tutabilmek için
Document’a, string ömrü'string’den ayrı olarak belge verisinin ömrü'doceklendi - Uygulamada
typed_arenacrate’i kullanıldıallocile bir değer arena’ya kaydedildiğinde o değerin referansı döndürülür- Tahsis edilen veri, arena hayatta olduğu sürece kullanılabilir
- Arena kapsam dışına çıktığında içindeki veriler de birlikte kaldırılır
alloc(&self, value: T) -> &mut Ttarafından döndürülen referans arena’dan daha uzun yaşayamaz; Rust’ın borrow checker’ı arena kaldırıldıktan sonra geçersiz referansları engeller
Tekrarlanan belgeleri bir kez tahsis edip yeniden kullanmak
- Arena yalnızca başka belgeleri referanslamak için değil, kod genelinde tekrarlanan
Document’ları önbelleğe almak için de kullanıldı String("fn"),String("pub"),String("type")gibi dil anahtar sözcüklerini temsil eden belgelerin her seferinde yeniden oluşturulmasına gerek kalmadı- Liste öğeleri arasında kullanılan
Break { unbroken: ", ", broken: "," }gibi belgeler de bir kez tahsis edilip yeniden kullanıldı - Daha önce sürekli
Boxiçine konması gereken yüzlerce küçük belge artık her biri yalnızca bir kez tahsis edilebilir hale geldi
Basit API ve geniş kapsamlı çağrı noktası değişiklikleri
- Arena API’sinin kendisi basit olsa da mevcut kodun büyük bir bölümünü dönüştürme sürecinde tekrarlayan değişiklikler gerekti
- Daha önce yalnızca
Box::newçağıran fonksiyonların artık veriyi tahsis edecek ek bir arena argümanı alması gerekiyor format_listgibi fonksiyonlar, liste öğelerini formatlayan alt fonksiyonlara da aynı arena’yı iletecek ve iç içe belgeleriBox::newyerinearena.allocile oluşturacak şekilde değiştirildi- Birkaç gün süren dikkatli arama-değiştirme sonunda çalışma
+2963/-1032büyüklüğünde bir pull request ile tamamlandı - Bu tekrarlayan ve manuel bir çalışmaydı; süreçte LLM kullanılmadı
Hız ve tepe bellek kullanımı iyileştirmeleri
- Gerçek bir Gleam projesi olan
squirrelformatlanırken pretty printer çalışma süresi 13 ms’den 9,8 ms’ye indi- Yalnızca pretty printer karşılaştırıldığında bu %24 performans artışı anlamına geliyor
gleam format, formatlamanın yanı sıra proje kaynak kodunu okuma ve ayrıştırma da yaptığından pretty printer toplam işin yalnızca bir bölümünü oluşturuyor; yine de toplam çalışma süresi %13 kısaldı- Tepe bellek kullanımı 8,4 MB’den 7,6 MB’ye düşerek yaklaşık %10 azaldı
- Arena ile ayrı heap tahsislerinin sayısı azaltılınca formatlayıcı daha hızlı çalıştı ve bellek kullanımı da birlikte düştü
1 yorum
Lobste.rs yorumları
En yüksek bellek kullanımını %10 daha azaltmak istiyorsanız, referanslar yerine opak handle döndüren başka bir arena düşünmeye değer. Handle'ları 64 bit yerine 16 bit veya 32 bit yapabildiğiniz için, çok sayıda küçük tahsis olduğunda etkisi büyük olur.
https://donsz.nl/blog/arenas/ adresine göre bunu destekleyen tek crate
compact_arena; ancak ihtiyaçlarınıza uymuyorsa kendiniz uygulamak da zor değil.Option<T>listelerini öğe başına yaklaşık 1 bitlik varlık bilgisi ve gerçekten var olan öğelerinTdeğerleri olarak ikiye ayırıp saklıyor.Bunun karşılığında referans türü olarak
&Option<T>değilOption<&T>döndürüyor; daha genel olarak da yapısal özyinelemenin içine referans koymak yerine, referansları izleyerek yapısal olarak özyinelenen bir yaklaşım kullanıyor.Arenalar daha yaygın kullanılmayı hak ediyor; handle veya akıllı işaretçi yerine referans döndürmek, Rust'ta özyinelemeli enum desen eşlemeyi çok kullanışlı hâle getiriyor. Normalde
if let MyAst::Add(MyAst::Multiply(a, b), c) = expr {}gibi yazmaya çalışsanız da aradaki box veya pointer yüzünden bu mümkün olmaz; ancak referanslar, nightly özellikler olmadan şeffaf biçimde desen eşleştirilebilir. Tüm referanslar tek bir arenayı işaret ediyorsa, ağacın tamamını birbirine bağlayan lifetime da tutarlı kalır.Bu yöntem, bir başka Wadler tarzı pretty-printing kütüphanesi olan
pretty'nin desteklediği yaklaşıma oldukça benziyor. Ancak birden fazla bellek tahsis yöntemini desteklediği için API'si biraz ağır gelebilir.Yazıda alıntılanan makalenin yazarının Brunswick'te sıradan bir BT şirketinde çalışıyor olması şaşırtıcı.