4 puan yazan GN⁺ 2024-03-20 | 1 yorum | WhatsApp'ta paylaş
  • 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_init adlı tek bir fonksiyon sağlar ve bu fonksiyonda üç callback başlatılır
    • compile_expr
    • release_context
    • reset_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
  • 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
  • CONST opcode stencil’ı op’u harici ExprEvalStep olarak bildirir ve derlenmiş .o dosyasında op adresinin 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 .o dosyası 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 where koş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.sh dosyası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

 
GN⁺ 2024-03-20
Hacker News yorumları
  • LLVM JIT derleyicisini daha kolay kullanılabilir hale getirmenin bir yolu olan derlenmiş sorgu önbelleği ve yeniden kullanım aslında LLVM’de yıllar önce uygulanmıştı :) https://github.com/llvm/llvm-project/commit/a98546ebcd2a692e...
    • Doğru, daha açık söylemeliydim; sorun LLVM’de değil, PostgreSQL tarafında
      JIT derleyicisinin bellek adreslerini doğrudan enjekte etmesi gerekiyor, bu yüzden üretilen kod ilgili sorguya ve sürece bağlı hale geliyor
    • PG her bağlantı için bir süreç kullanıyor ve LLVM JIT kodu süreç başına sabit olduğundan, DB’deki tüm bağlantılar kodu paylaşamıyor
      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
  • Yazının yazarıyım. Yazıyı Hacker News’e taşıdığınız için teşekkürler; sorular varsa elimden geldiğince yanıtlamaya çalışırım
    • C’yi hedefleyen copy-and-patch ile derleyicinin ara gösterimi hedeflediğinde yaptığı iş arasında temel bir fark olup olmadığını merak ediyorum
      Geleneksel derleme yöntemi de C değil başka bir ara dil kullanan “copy-and-patch” gibi görünüyor
    • Postgres performans iyileştirmeleri görmek her zaman güzel. Gerçek sorgularda LLVM’de geçen sürenin ne kadar olduğunu ve LLVM ayarlarının nasıl olduğunu merak ediyorum
      Örneğin hangi pass’lerin, hangi arka uç optimizasyonlarının kullanıldığı gibi
      Bizim deneyimimize göre [1], optimizasyonu kapatıp -O0 arka 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ıyor
      Ayrı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
    • copy-and-patch gerçekten yeni bir fikir mi, yoksa eski bir fikre yeni bir ad mı verilmiş, merak ediyorum
      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 memcpy ile kopyalanabileceğinin iyi bilinen bir gerçek olduğunu düşünüyordum
      O 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
    • Hazirandaki pgconf.eu için çok iyi bir konu olabilir. pgcon Vancouver’a taşındı
      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
  • PostgreSQL’de hâlâ sorgu/yürütme planı önbelleği olmaması şaşırtıcı
    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
    • PG’de de yürütme planı önbelleklemenin bir biçimi var. Hazırlanmış ifadelerde, gerçek parametre değerlerinin yürütme planını çok etkilemediğine karar verirse PG “generic plan” kullanır ve aynı hazırlanmış ifadenin tüm çalıştırmalarında aynı yürütme planını yeniden kullanır
      https://www.postgresql.org/docs/current/sql-prepare.html içindeki “notes” bölümüne bakın
    • Ben de başta aynı fikirdeydim, ama bunu kendim yaptıktan sonra gerçekten zor olacağını anladım
      Ç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
    • Oracle’ın yürütme planı önbelleği, optimizer hazırlanmış ifadenin parametrelerine bakamadığında geçmişte epey baş ağrıtmıştı
      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 tablodaki x değerlerinin dağılımına bağlı olarak kâbusa dönüşebiliyordu
      Postgres’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
  • 386 döneminde assembly’de kendini değiştiren kod diye bir kavram vardı
    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