PostgreSQL için yeni bir JIT derleyici geliştirildi
(pinaraf.info)- Deneysel JIT motoru pg-copyjit, LLVM’den daha düşük üretim maliyetiyle kısa PostgreSQL sorguları için de “yeterince hızlı” kod üretmeye odaklanıyor
- PostgreSQL’in cost tahminleri gerçek çalışma süresiyle doğrudan örtüşmediğinden, optimizasyon maliyeti yüksek LLVM JIT kısa sorgularda tersine zarar ettirebilir
- copy-and-patch yöntemi, C ile yazılmış stencil’ları önceden derleyip çalışma anında gerekli parçaları kopyalayıp yamalayarak yeni bir fonksiyon gibi çalıştırır
- pg-copyjit, PostgreSQL JIT provider arayüzü üzerinden bağlanır; uygulanmamış bir opcode ile karşılaşınca otomatik olarak PostgreSQL yorumlayıcısına geri düşer
- Şu anda PostgreSQL 16 ve AMD64 üzerinde çalışan bir kavram kanıtı aşamasında; kod üretimi yüzlerce mikrosaniye düzeyinde olsa da derleme, dokümantasyon ve destek henüz hazır değil
pg-copyjit’in hedeflediği nokta
- pg-copyjit, PostgreSQL sunucusunu daha hızlı hâle getirmeye yönelik deneysel bir JIT motorudur
- Mevcut kod, üretim sunucularından çok uzman hacker’ların denemelerine uygun bir seviyeye daha yakın
- Beklenen geri bildirimler ilginç deney sonuçları, performans artışı örnekleri ve uygulama fikirleri; iş açısından kritik uygulamalarda kesinti riskini göze alma aşamasında değil
PostgreSQL’de LLVM JIT’in neden yük getirdiği
- PostgreSQL’de zaten Andres Freund tarafından eklenen LLVM tabanlı bir JIT derleyici var
- LLVM verimli kod üretebilir, ancak PostgreSQL’de kullanıldığı biçimde JIT derleme ve optimizasyon maliyeti yüksek
- Optimize edici kullanılmazsa, hiç derlememekten daha kötü olabilir; optimize edici kullanılırsa maliyet daha da artabilir
- JIT’in uygulanıp uygulanmayacağına karar vermek için genel query cost estimation kullanılır
- PostgreSQL’deki cost, sorguları birbiriyle karşılaştırmaya yarayan bir değerdir; gerçek çalışma süresini ifade etmez
- Cost değeri 100 olan bir sorgu 1 saniye sürebilir, cost değeri 1000 olan bir sorgu 100 ms’de bitebilir
- Bir sorgu 10 ms hızlansa bile optimizasyon 50 ms sürüyorsa toplam çalışma süresi açısından zarar edilir
- LLVM JIT’i daha kullanışlı kılmanın bir yolu, derlenmiş sorguları önbelleğe almak ve yeniden kullanmaktır; ancak bunu uygulamak küçük bir iş değildir
copy-and-patch yönteminin yapısı
- copy-and-patch, 2021’de bir makaleyle tanıtılan bir JIT derleyici oluşturma yöntemidir ve Python 3.13 JIT motorunda da kullanılır
- Temel birim, C ile yazılmış stencil’dır
- Stencil, içinde boşluklar bulunan bir fonksiyondur
- clang ile önceden derlenir
- gcc desteği şimdilik beklemede
- Derleme anında gerekli stencil’lar birbirine eklenir, boşluklar doldurulur ve yeni oluşturulan “derlenmiş” fonksiyona atlanır
- Temel akış basittir
- Stencil yeni bir bellek alanına kopyalanır
- Gerekli değerler yamalanır
- Ortaya çıkan kod çalıştırılır
- Ek optimizasyon imkânları da vardır
- Derleme anında hesaplanabilecek değerler önceden hesaplanır
- Döngüler birden fazla stencil’a bölünüp unroll edilir
- Birden fazla stencil birleştirilerek tek seferde optimize edilen bir meta-stencil oluşturulabilir
PostgreSQL’e bağlama yöntemi
- PostgreSQL’in JIT’i genişletilebilir bir provider yapısına sahiptir
.so,_PG_jit_provider_initadlı tek bir fonksiyon sağlar ve bu fonksiyonda üç callback başlatılırcompile_exprrelease_contextreset_after_error
- Temel callback
compile_expr’dir- Girdi, opcode’lardan oluşan ifade işaretçisi
ExprState*’dır - Opcode’ları istenen yöntemle derler
- Üretilen kodu çalıştırılabilir olarak işaretler
evalfunc’ı PostgreSQL yorumlayıcısı yerine üretilen kodla değiştirir
- Girdi, opcode’lardan oluşan ifade işaretçisi
- Uygulanmamış bir opcode ile karşılaşınca otomatik olarak PostgreSQL yorumlayıcısına geri düşülebilir
pg-copyjit’in derleme süreci
- pg-copyjit’in copy-and-patch algoritması şu anda yalnızca birkaç küçük optimizasyon içeren basit bir biçimdedir
- Her opcode için derleyici stencil koleksiyonunu kontrol eder
- Opcode’a karşılık gelen stencil varsa üretilmekte olan koda ekler
- Stencil yoksa derlemeyi durdurur ve çalıştırmayı PostgreSQL yorumlayıcısına bırakır
- Stencil eklendikten sonra her boşluk gerekli değerle yamalanır
CONSTopcode stencil’ıop’u hariciExprEvalStepolarak bildirir ve derlenmiş.odosyasındaopadresinin gireceği bir boşluk bırakır- Stencil koleksiyonu bu relocation bilgisini korur; JIT derleyici mevcut opcode yapısının adresini yerleştirerek çalıştırılabilir kod üretir
- Derleme akışı önce stencil’ları tek bir
.odosyası hâline getirir, ardından buradan assembly kodunu ve relocation’ları çıkarıp C’de kullanılacak yapılara dönüştürür
Uygulama durumu ve performans
- Başlangıçta assembly kodu elle çıkarılarak
SELECT 42;için gereken 3 opcode çalıştırıldı - Daha sonra assembly kodu çıkarımını otomatikleştiren bir DirtyPython betiği yazıldı ve birkaç saat içinde şu özellikler eklendi
- Fonksiyon çağrıları
- Tek tablo sorguları
- Daha karmaşık veri tipleri
- Birkaç optimizasyon
- Şu anda doğrulanan durum şöyle
- PostgreSQL 16’da çalışıyor
- Önceki sürümlerde de muhtemelen sorun olmayacağı düşünülüyor, ancak doğrulanan ortam PostgreSQL 16
- Desteklenen tek mimari AMD64
- ARM64 desteği ekleme planı var
- POWER64 ve S390x gibi hedefler de ilgi alanında, ancak derleyici yamaları ve ilgili makinelere erişim gerekebilir
- Performans rakamları, henüz neredeyse hiç optimize edilmemiş durumdaki sonuçlardır
- Kod üretimi yüzlerce mikrosaniye içinde tamamlanıyor
- Kısa sorgularda da kullanılabilecek seviyede
SELECT 42;için no JIT 0,3 ms, copyjit 0,6 ms, optimizasyonsuz LLVM 1,6 ms, optimizasyonlu LLVM ise 6,6 ms sürüyor
- LLVM çok hızlı kod üretebilir, ancak pg-copyjit’in amacı hızlı biçimde yeterince hızlı kod üretmek olduğundan iki aracı doğrudan karşılaştırmak zordur
- İndekssiz basit 90k rows bir tabloda iki sorgu benchmark edildi; CPU işi içeren bir
wherekoşulu eklendiğinde yorumlayıcıya göre performans iyileşiyor - Benchmark’lar dizüstü bilgisayarda yapıldığı için güvenilirlikleri sınırlı; ileride masaüstünde daha uygun benchmark’lar yapılması planlanıyor
- Uygulanan opcode sayısı hâlâ az olsa da herhangi bir sorgu çalıştırılabilir
- JIT motoru uygulanmamış kısımlar için mesaj verir
- Gerçek çalıştırmayı yorumlayıcı üstlenir
Kodun yayımlanması ve kalan işler
- Kod, GitHub’daki pg-copyjit deposunda yayımlandı
- Şu anda git history düzenleme veya dokümantasyondan çok kodun kendisine odaklanılmış durumda
- Derlemek için önce
build-stencils.shdosyasını elle çalıştırmak gerekiyor - Mevcut durumda destek verilemediği için dokümantasyon da henüz hazırlanmadı
- Kalan işler görece net
- Daha fazla opcode uygulamak
- Optimizasyonları araştırmak
- Derleme kolaylığını iyileştirmek
- Paketlenebilir hâle getirmek
- Derleme betiği şu anda Debian ve PostgreSQL 16’ya özel
Beklenen kullanım alanları ve mimari genişletme
- Hedef, güvenli biçimde paketlenip kendi üretim sunucularına dağıtılabilecek seviyeye ulaşmak
- JIT’i sunucu türüne göre ayırarak kullanma fikri de var
- GIS sunucuları gibi sorguların optimizasyon maliyetini karşılamaya değdiği yerlerde LLVM JIT kullanmak
- Kısa sorgu süresinin önemli olduğu web uygulaması veritabanlarında pg-copyjit kullanmak
- Başka mimarilere port etmek de ciddi bir hedef
- Alpha, Itanium, Sparc, M68k gibi çeşitli mimarilerin dönemini özlediğini ve tek mimari merkezli monoculture sorununa ortak olmak istemediğini belirtiyor
1 yorum
Hacker News yorumları
JIT derleyicisinin bellek adreslerini doğrudan enjekte etmesi gerekiyor, bu yüzden üretilen kod ilgili sorguya ve sürece bağlı hale geliyor
Yürütme planı da farklı süreçler arasında paylaşılmak üzere tasarlanmış bir bellek veri yapısı olmadığı için aynı sorunu yaşıyor
MSSQL gibi DB’lerde iş parçacıkları kullanan tek süreçli bir yapı olduğundan bu sorun yok; dış bir havuzlayıcı olmadan daha fazla eşzamanlı bağlantıyı işleyebilmelerinin nedenlerinden biri de bu
MSSQL ayrıca yürütme planını sürece bağlı olmayan bir temsile serileştirip DB’de saklayabiliyor; bu da yürütme planını sabitleme gibi özelliklerde kullanılabiliyor
Geleneksel derleme yöntemi de C değil başka bir ara dil kullanan “copy-and-patch” gibi görünüyor
Örneğin hangi pass’lerin, hangi arka uç optimizasyonlarının kullanıldığı gibi
Bizim deneyimimize göre [1], optimizasyonu kapatıp
-O0arka uç pipeline’ı ile derleme süresine göre ayar yaparsanız LLVM de epey hızlanabiliyor, ama yine de diğer yaklaşımlardan 10–20 kat yavaş kalıyorAyrıca copy-and-patch ile üretilen kodun çalışma hızının epey yavaş olduğunu ve optimize etmenin zor olduğunu deneyimledik. Birkaç şey denedik ama [2; Sec. 5], hâlâ ciddi fark var. Veritabanı değerlendirme sonuçları için Fig. 3’e bakın
LLVM’e kıyasla çalışma süresindeki düşüşe dair rakamlarınız var mı; hızlı derlenen koddan LLVM ile optimize edilmiş koda dinamik olarak geçiş yapan çok aşamalı JIT uygulama planınız olup olmadığını da merak ediyorum
[1]: https://home.in.tum.de/~engelke/pubs/2403-cgo.pdf
[2]: https://home.in.tum.de/~engelke/pubs/2403-cc.pdf
2010 civarında programlama, özellikle de yorumlayıcılar öğrenirken, dikkatli kullanılırsa derleyicinin ürettiği yürütülebilir kod parçalarının
memcpyile kopyalanabileceğinin iyi bilinen bir gerçek olduğunu düşünüyordumO dönemdeki büyük tuzak, NX bitinin yeni yeni yaygınlaşmaya başlamasıydı. Linux’ta bile çoğu kişi hâlâ 32 bit dağıtımları doğal kabul ediyordu; CPU’nun 64 biti desteklediğini öğrenince şaşıranlar da vardı
Daha sonra 64 bit kodu hiç desteklemeyen bir netbook bile kullandım
Ne yazık ki kodun geri kalanına çok fazla zaman harcadığım için bunu yeterince derinlemesine kurcalayıp işe yarar bir şey yapamadım
Ne yazık ki konuşma önerisi çağrısı kapandı, ama bir “unconference” bölümü var. Yine de konular etkinlikte belirlendiği için garanti değil
Bir önbellek gelirse sık çalıştırılan sorgular önbelleğe alınır ve daha agresif optimize edilir; böylece derleme maliyeti telafi edilerek iki yaklaşımın iyi örtüşmesi mümkün görünüyor
Elbette bu, kendi başına tamamen yeni bir karmaşıklık ve baş ağrısı katmanı ekler
https://www.postgresql.org/docs/current/sql-prepare.html içindeki “notes” bölümüne bakın
Çok basitleştirirsek, yürütme motorunun birçok yerinde sorgunun bazı bölümlerine işaret eden pointer’lar sızıyor
Bunları ortadan kaldırmak için yürütme motoru, planlayıcı ve başka neleri de kapsayan kayda değer ölçekte kapsamlı bir elden geçirme gerekiyor
Tek bir oturum içinde bile iki derlenmiş sorgu bu yüzden farklı derlenmiş koda sahip oluyor. Hem LLVM hem de benim copyjit’im, çeşitli struct’ların adreslerini assembly koduna enjekte etmek zorunda
Genellikle elle girip hint eklemek ya da yürütme planını zorlamak gerekiyordu
SELECT * FROM t WHERE x = TRUE;gibi basit bir sorgu bile tablodakixdeğerlerinin dağılımına bağlı olarak kâbusa dönüşebiliyorduPostgres’te bu tür sorunlarla pek karşılaşmadım; ancak Postgres’i hazırlanmış ifadelerle kullanmadığımı kabul etmeliyim
Planlama süresi yavaş olan sorgular (100 ms üstü) gördüm, dolayısıyla önbellek faydalı olabilirdi; ama gerçekten optimizasyonun şart olduğu bir durum hatırlamıyorum
Burada sunulan stencil’lere benziyor; fakat kod genellikle tek bir örnek olduğundan kopya oluşturmak nadirdi
Örneğin DOS’taki Doom da bu optimizasyon tekniğini kullanıyordu. Eski CPU’larda sıkı render döngülerinden yeterli performansı almak için gerekliydi