3 puan yazan GN⁺ 2025-03-11 | 1 yorum | WhatsApp'ta paylaş
  • CPython'ın yeni kuyruk çağrısı yorumlayıcısı ilk başta pyperformance ortalamasında %10~15 iyileşme sağlıyormuş gibi göründü, ancak referans çizgisi düzeltildiğinde gerçek kazanımın yapılandırmaya göre yaklaşık %1~5 aralığına indiği görüldü
  • Büyük iyileşmenin, yeni uygulamanın kendi etkisinden çok LLVM 19 regresyonunu dolaylı olarak aşmasından kaynaklandığı anlaşıldı; asıl belirleyici etken, Clang 19'un mevcut computed goto dispatch yapısını doğru biçimde kopyalayamamasıydı
  • Intel Raptor Lake i5-13500 üzerinde clang19 derlemesi clang18'den 1,09 kat daha yavaştı ve clang19.tc 1,03 kat daha hızlıydı; ancak Apple M1 MacBook Air'de clang19 1,12 kat daha yavaştı ve clang19.tc de 1,00 kat daha yavaş seviyesinde kaldı
  • LLVM'nin tail duplication sınırı nedeniyle dolaylı atlama sayısı clang18'deki 332'den clang19'da 3'e düştü ve böylece computed goto tabanlı yorumlayıcının amaçlanan yapısı fiilen ortadan kalktı
  • Kuyruk çağrısı yaklaşımı hâlâ anlamlı bir iyileştirme sunuyor, ancak musttail gibi derleyici optimizasyonunun uygulanmasını açıkça zorunlu kılan bir yaklaşım performansa duyarlı kod için daha sağlam olabilir

Performans iyileştirmesi gibi görünen referans çizgisi etkisi

  • CPython projesi yaklaşık bir ay önce bayt kodu yorumlayıcısı için yeni bir uygulama stratejisini birleştirdi
  • İlk sonuçlar, çeşitli platformlarda ve pyperformance kıyaslamalarında ortalama %10~15 performans artışı gösterdi
  • Sonraki analizler, bu büyük artışın esas olarak LLVM 19 regresyonunun tesadüfen aşılmasından kaynaklandığını ortaya koydu
    • GCC, clang-18 ya da belirli ayar bayrakları uygulanmış LLVM 19 ile karşılaştırıldığında artış yaklaşık %1~5 düzeyine düşüyor
  • Kuyruk çağrısı yorumlayıcısının kendisi gerçekten hız kazancı sağlıyor, ancak iyileşme ilk sayıların ima ettiğinden daha sınırlı
  • Eğer clang-19 veya sonrası ile derleme yaptıysanız, mevcut yol gerçekte %10~15 daha yavaş olmuş olabilir
    • Simon Willison, python-build-standalone derlemesi ile Python 3.13 karşılaştırmasında %10 hız artışını yeniden üretti

Benchmark yapısı ve temel sayılar

  • Birden fazla CPython derlemesi Intel sunucuda ve Apple M1 MacBook Air üzerinde karşılaştırıldı
    • Intel sunucu, Hetzner tarafından işletilen Raptor Lake i5-13500 idi
    • Tüm derlemelerde LTO ve PGO kullanıldı
    • Derleme yeniden üretimi için nix yapılandırması kullanıldı
  • Karşılaştırılan sürümler şunlardı
    • clang18: Clang 18.1.8, computed goto
    • gcc: GCC 14.2.1, computed goto, yalnızca Intel için
    • clang19: Clang 19.1.7, computed goto
    • clang19.tc: Clang 19.1.7, yeni kuyruk çağrısı yorumlayıcısı
    • clang19.taildup: Clang 19.1.7, computed goto ve regresyonu aşmak için -mllvm ayar bayrağı
  • clang18 referans alındığında pyperformance ortalama sonuçları şöyleydi
    • Raptor Lake i5-13500:
      • clang19: 1,09 kat daha yavaş
      • clang19.taildup: 1,01 kat daha hızlı
      • clang19.tc: 1,03 kat daha hızlı
      • gcc: 1,02 kat daha hızlı
    • Apple M1 MacBook Air:
      • clang19: 1,12 kat daha yavaş
      • clang19.taildup: 1,02 kat daha yavaş
      • clang19.tc: 1,00 kat daha yavaş
  • Kuyruk çağrısı yorumlayıcısı, clang-18'e göre bir miktar hız artışı gösterdi, ancak clang-19'a geçişte ortaya çıkan performans kaybından daha küçük kaldı
  • clang18.tc ölçülemedi
    • Çünkü kuyruk çağrısı yorumlayıcısı Clang 19'a yeni eklenen bir derleyici özelliğine bağlı
    • Bu kısıt nedeniyle durumu anlamak için daha fazla benchmark kombinasyonu gerekti

LLVM 19 regresyonunun yarattığı dispatch çöküşü

  • Geleneksel bir bayt kodu yorumlayıcısı, opcode'ları while döngüsü içindeki switch ifadesiyle işler
    • Derleyici genellikle switch yapısını bir jump table ve dolaylı atlama olarak derler
  • Uzun zamandır, her opcode gövdesine dispatch mantığını kopyalamanın bu tür yorumlayıcıları hızlandırabildiği biliniyor
    • Yani opcode sonunda döngünün başına dönmek yerine, bir sonraki komutu çözümleme ve jump table indeksleme mantığı her opcode için ayrı yer alır
  • C derleyicileri, etiket adreslerini alıp bunları computed goto olarak kullanma özelliği sunar ve CPython da kuyruk çağrısı çalışmasından önce bu yönteme dayalı bir yorumlayıcı döngüsü kullanıyordu
  • Clang/LLVM, derleyici performansı gerekçesiyle computed goto içindeki çok sayıdaki gotoyu dahili olarak tek bir indirectbr LLVM instruction içinde birleştirir
    • Ardından kod üretim aşamasında tail duplication yaparak dallanma mantığını her konuma yeniden kopyalar
    • Bu akış, LLVM'nin eski bir blog yazısında üst düzeyde belgelenmiştir
  • LLVM 19, belirli durumlarda ciddi derleme süresi veya bellek kullanım artışını önlemek için tail duplication pass sınırı getirdi
    • CPython'da bu sınır, Clang'in dispatch atlamalarını birleştirilmiş halde bırakmasına neden oldu
    • Sonuç olarak computed goto tabanlı uygulamanın amacı fiilen boşa çıktı
  • Bu sorun, benzer yorumlayıcı döngülerine sahip başka dil uygulamalarında daha önce tespit edilmişti, ancak CPython'ı etkilediği bilinmiyordu
  • Nesne kodu disassemble edilip dolaylı atlama sayısı sayıldığında fark doğrudan görülüyor
    • clang18 derlemesindeki _PyEval_EvalFrameDefault: jmp * 332 adet
    • clang19 derlemesindeki _PyEval_EvalFrameDefault: jmp * 3 adet

computed goto'nun belirsiz konumu

  • Tail duplication mantığındaki değişimin regresyona yol açtığı, düzeltme sonrası clang-18 düzeyi performansın geri gelmesiyle doğrulandı
  • Ancak regresyonun büyüklüğü tamamen açıklanmış değil
    • Tarihsel olarak opcode dispatch kopyalamasının yorumlayıcıyı %20 ila %100 hızlandırdığı aktarılmıştı
    • Modern işlemcilerde gelişmiş dallanma kestiricileriyle daha yeni araştırmalar yaklaşık %2~4 gibi daha küçük bir artış gösteriyor
  • Python, yapılandırma seçeneği olarak tek bir switch ifadesi kullanan eski tip yorumlayıcıyı da destekliyor
    • clang18.nocg: clang18'e göre 1,01 kat daha hızlı
    • clang19.nocg: clang18'e göre 1,02 kat daha yavaş
    • clang19: clang18'e göre 1,09 kat daha yavaş
  • clang19.nocg sürümünün clang19'dan daha hızlı çıkması ek bir ters köşe oldu
    • Clang 18 veya uygun bayraklarla ayarlanmış Clang 19, switch tabanlı yorumlayıcıda da dispatch mantığını her opcode gövdesine kopyalıyor
  • Dolaylı atlama sayılarının karşılaştırması da bu farkı gösteriyor
    • clang18: 332
    • clang18.nocg: 306
    • clang19.nocg: 3
    • clang19: 3
  • Modern Clang ile computed goto yorumlayıcısının tamamı gereksiz karmaşıklık olabilir
    • Çünkü derleyici aynı dönüşümü switch tabanlı kodda da yapabiliyor
    • Tersine, computed goto'nun kendisi de bu dönüşümü garanti etmeye yetmedi
  • GCC 14.2.1, switch yapısını kopyalamadı ama computed goto kullanıldığında amaçlanan davranışı gerçekleştirdi

Düzeltme ve geçici çözüm yöntemleri

  • LLVM pull request 114990, yazı yayımlandıktan hemen sonra birleştirildi ve regresyonu düzeltti
  • Birleştirilmeden önceki benchmark'larda da bu düzeltmenin beklenen performansı geri getirdiği doğrulandı
  • Düzeltme öncesi sürümlerde, regresyona yol açan PR'ın eklediği ayar seçeneğiyle tail duplication durma eşiği değiştirilebiliyor
    • clang-19'da bu sınır çok büyük bir değere ayarlanırsa benzer davranış geri kazanılabiliyor
  • LTO derlemelerinde bu seçeneği iletmek karmaşık
    • Tail duplication, derleme sırasında değil kod üretimi sırasında gerçekleşir ve LTO derlemelerinde kod üretimi derleme zamanında değil link zamanında olur
    • Bu nedenle bayrağın yalnızca derleyiciye değil lld'ye de verilmesi gerekir
  • Kullanılan yapılandırma örneğinde ./configure aşamasında OPT ve LDFLAGS içine -mllvm -tail-dup-pred-size=5000 ekleniyordu

Benchmark'ın ortaya çıkardığı referans çizgisi sorunu

  • Benchmark'lar belirli derlemeler arasındaki performans farkını doğru ölçebilir, ancak bu sonucu “genel performans artışı” olarak genellemek ek varsayımlar gerektirir
  • Kuyruk çağrısı yorumlayıcısı benchmark'ları, mevcut computed goto yorumlayıcısına göre %10~15 daha hızlı sonuç verdi, ancak bunu daha geniş bir sonuca genellemek için referans çizgisi karmaşıktı
  • Performans çalışmalarında neyin referans olarak alındığı, tekrar tekrar zorlayıcı bir sorundur
    • Kuramsal olarak bilinen en iyi yaklaşımı anlamak başka, gerçek işletim sistemi, derleyici seçenekleri ve bayrakları doğru ayarlamak başka bir meseledir
    • Eski donanımda ya da yeniden üretmesi zor ölçekte yapılmış kamuya açık benchmark'lar doğrudan karşılaştırma için uygun olmayabilir
  • Makine öğrenimi makalelerinde de algoritma iyileştirmesi iddia edildiğinde, çoğu zaman ilk önemli soru “ne yapıldı?” değil “hangi referansla karşılaştırıldı?” olur
  • Kötü ayarlanmış bir referans çizgisiyle karşılaştırıldığında etkileyici sonuçlar elde etmek kolay olabilir

Optimize edici derleyiciler ve musttail

  • computed goto örneği, optimize edici derleyicilerden beklenen şeylerin birbiriyle çatışabileceğini gösteriyor
    • Derleyici programcının niyetine saygı duymalı ve aynı davranışı korumalıdır
    • Aynı zamanda kodu hızlandırmak için karmaşık ve sezgisel olmayan dönüşümler de uygulayabilmelidir
  • clang-19, computed goto yorumlayıcısını program davranışı açısından doğru derledi, ancak optimizasyon niyetiyle tamamen farklı bir çıktı üretti
  • Clang'in diğer sürümleri, basit switch() tabanlı yorumlayıcıya bile amaçlananla aynı optimizasyonu uyguladı
  • Kaynak kodu düzeyindeki computed goto ile makine kodu düzeyindeki dispatch kopyalaması neredeyse birbirinden bağımsız kavramlar gibi görünüyor
    • Çalışma sonucu aynı olduğundan, mevcut araçlar bu farkı tutarlı biçimde ifade etmekte zorlanıyor
  • Kuyruk çağrısı yorumlayıcısı musttail attribute temellidir
    • musttail, geleneksel anlamda gözlemlenebilir program davranışını değiştirmez, ancak daha çok optimizasyon aracıyla bir diyalog gibidir
    • Derleyicinin belirli bir optimizasyonu uygulayabilmesi gerekir ve bu optimizasyon gerçekleşmezse derlemenin başarısız olması istenir
  • Bu yaklaşım, derleyiciler evrilirken bile performansa duyarlı kodu daha sağlam yazmanın bir tarzı olabilir
  • Yorumlayıcının while döngüsündeki computed goto'yu varsayımsal bir [[clang::musttailduplicate]] benzeri öznitelikle değiştirmek de değerlendirilebilir

nix'in sağladığı yeniden üretilebilirlik ve sınırlamalar

  • nix, çok sayıda Python yorumlayıcı derlemesini yönetmede büyük fayda sağladı
    • Deney sürecinde gcc, clang-18, clang-19, clang-20 olmak üzere dört derleyici ve çeşitli bayrak kombinasyonlarıyla onlarca Python yorumlayıcısı derlenip benchmark edildi
  • nix sayesinde paralel sürümler yeniden üretilebilir ve yalıtılmış biçimde korunabildi
    • Belirli bir derlemenin hangi derleyici ve bayraklarla üretildiğinden emin olunabildi
    • Derleme matrisi tanımı da kısa soyutlamalarla yönetilebildi
  • Hata düzeltme yaması uygulanmış özel bir LLVM derleyip, bu derleyiciyle Python derlemesi yapmak da yaklaşık 10 satır kodla mümkün oldu
  • Dezavantajlar da varlığını sürdürdü
    • nix, genel yazılım kullanım biçiminden farklı yönlere sahip olduğu için, bu farklılıkların benchmark ya da sonuçları etkilemiş olma ihtimalini tamamen dışlamak zor
    • Örneğin nix varsayılan olarak projeleri belirli hardening bayraklarıyla derliyor ve bu bayrakların kuyruk çağrısı yorumlayıcısını orantısız biçimde etkilediği erken aşamada fark edildi
  • Nix güçlü ölçeklenebilirlik ve özelleştirme sunuyor, ancak belirli özelleştirme yöntemlerini bulmak için çok sayıda deneme-yanılma ve nixpkgs kaynak kodu incelemesi gerekebildi

1 yorum

 
GN⁺ 2025-03-11
Hacker News yorumları
  • CPython’a kuyruk çağrısı yorumlayıcısını ekleyen PR’ın yazarıyım.
    Öncelikle bu sorunun nedenini bulmak için neredeyse bir ay harcayan Nelson’a teşekkür ederim.
    Temel çizgi için kullandığımız derleyicide böyle bir hata olacağını ne ben ne de muhtemelen CPython ekibi bekliyordu; büyük bir hata yaptığım için çok utanıyor ve özür diliyorum.
    Bir özür yazısı da yayımladım: https://fidget-spinner.github.io/posts/apology-tail-call.htm...

    • “Büyük bir hata olduğu için çok utanıyor ve özür diliyorum” ifadesini görünce CPython’ın performansını bozduğunu sandım, ama gerçekte durum hiç de öyle değil.
      %10~15 performans artışı diye duyurulmuştu, fakat hatasız bir derleyicide bunun %1~5’e daha yakın olduğu söyleniyor; o rakam da tamamen yanlış değil, yalnızca belirli koşullarda doğruymuş.
      İyileştirmeyi yapmış, ölçmüş ve PR’ı da inceletmiş; yani yapılması gerekeni yapmış sayılır. Ölçümde kullanılan clang sürümündeki sorun nedeniyle rakamların yanlış anlaşılmaya yol açması, herkesin düşebileceği makul bir hata gibi görünüyor.
      Yine de anlamlı bir performans iyileştirmesi sağladı ve bir derleyici regresyonunu da ortaya çıkardı; yanlış rakamlar bunların yanında küçük görünüyor. Bu işten gerçekten kimin zarar gördüğünden de emin değilim; özür gerektirecek bir durum gibi gelmiyor.
    • Bu arada, o blog yazısı yazıldıktan sonra düzeltme birleştirildi ;)
      Python yorumlayıcısı gibi eski bir sistemde %3~5 kalıcı oluyorsa bu tek başına bile büyük bir başarıdır; bununla gurur duymak gayet yerinde.
      Yaklaşık 30 yılın ardından, uzun süredir var olan sistemlerde anlamlı performans artışlarına, özellikle %1’i aşanlara, önce şüpheyle bakar oldum.
      Gerçek iyileştirmeler elbette var, ama yaygın değiller; bazen de yalnızca zamanı başka bir yere taşımış oluyorsunuz ve bu benchmark’ta görünmüyor. Ayrıca benchmark’lar etkiyi izole etmek için kontrollü ortamlarda yapılır, oysa gerçek yazılımlar VM’lerde veya masaüstlerinde başka pek çok şeyle birlikte çalışır.
      İzole bir ortamda bariz şekilde büyük görünen iyileştirmelerin üretim ortamına girince kaybolduğunu ya da negatife döndüğünü çok gördüm.
      CPython’ın birçok ortamı hedeflemesi gerektiği için iş daha da zor; “üretimde hızlanmıyorsa gerçekten hızlanmamıştır” denebilecek tek bir üretim hedefi de yok. Böyle bir dünyada performansı iyileştirmeye çalışmak gerçekten zor.
      Sonuçta performans ayarlama ve ölçüm çok zor; özür dilenecek şey olsa olsa bu gerçeği öğrenmiş olmaktır.
      Yanılmaktan korkmamanızı umarım. Zaten herkes yanılır. Şimdiki gibi “galiba bunu biz batırdık” deyip, bununla nasıl başa çıkılacağını ve gelecekte nasıl önleneceğini bulmak yeterli.
      [1] Bu yalnızca performansta değil, insanların süreçlerinde de yaygındır. Örneğin bir kod inceleme aracı ekibi “kod inceleme süresini %15 azaltarak herkesin iş akışını hızlandırdık” dese bile, gerçekte sistemin başka bir bölümünde daha fazla iş yaratmış, genel akışı hızlandırmamış ve %15’i ölçülmeyen bir yere taşımış olabilir.
    • Kuyruk çağrısı yorumlayıcısı tasarımının başlıca motivasyonlarından birinin optimize edicinin kaprislerine daha az açık olmak olduğunu düşünüyorum. Bu tekniği ele alan özgün yazıda da (https://blog.reverberate.org/2021/04/21/musttail-efficient-i...) böyle açıklanmıştı.
      Teoride, böyle bir kontrol akış grafiği ve profil olduğunda derleyicinin geleneksel switch() tabanlı bir yorumlayıcı için en iyi kodu üretmeye yetecek bilgiye sahip olması gerekir. Ama pratikte işlev bu kadar büyük ve birbirine bağlı olduğunda derleyiciyle mücadele etmeye başlarsınız.
      Register’da tutmak istediğiniz önemli değişkenleri spill eder, fallback işlev çağrıları etrafına sıkıştırmak istediğiniz stack frame işlemlerini yukarı çeker, dallanma tahmini yüzünden ayırmak istediğiniz aynı kod yollarını birleştirir. Eldivenle piyano çalmak gibi hissettirebilir.
      Burada da tam olarak o “aynı kod yolu birleştirme” gerçekleşti; “hatalı” derleyici aynı yolları birleştirerek performansı kötüleştirdi.
      “Düzeltilmiş” derleyici artık bunu yapmıyor, ama o düzeltme de nihayetinde derleyicinin iç sezgisellerini ayarlamaya yakın bir şey. Bu derleyicinin ya da başka derleyicilerin ileride de sezgisellerini bizim lehimize olacak şekilde koruyacağının garantisi yok.
      Buna karşılık kuyruk çağrısı yorumlayıcısı, istenen makine kodu desenini yorumlayıcının kendisinde ifade edebilir. musttail, noinline, preserve_none öznitelikleri birlikte kullanıldığında problem, optimize edici sezgisellerinden çok daha az etkilenilecek şekilde kısıtlanabilir.
      Bu yüzden kuyruk çağrısı yorumlayıcısının faydası basit bir %3~5 performans artışından fazlasıdır; bazı derleyicilerde bundan daha büyük, güvenilir performans artışı da sağlayabilir.
    • “Üzgünüm, hata yaptım” diyebilen tavra saygı duyuyorum. Günümüzde norm gibi görünen sahte davranıp başarılı olmuş gibi yapma kültüründen gerçekten nefret ediyorum.
    • Temel çizgi performans regresyonunun faster-cpython benchmark sayfasında [0] neden görünmediğini, ya da görünüp görünmediğini merak ediyorum.
      Benzer şeyleri önlemek için benchmark’lar iyileştirilebilir mi?
      [0] https://github.com/faster-cpython/benchmarking-public
  • Benchmarking işini düzgün yapmak gerçekten delirtici derecede zor. İnsanları yanıltan çok fazla unsur var.
    Yakın zamanda bir algoritmayı yaklaşık %15 hızlandırmanın bir yolunu bulduğumu sanmıştım. En azından tüm benchmark’lar öyle söylüyordu.
    Ama test düzeneğine daha hızlı fonksiyonu kopyalayıp koyduğumda ve aslında onu hiç çağırmadan yalnızca eski yavaş sürümü çağırdığımda bile hâlâ %15 daha hızlıydı. Yani hiç çalıştırılmayan kod, asıl kodu hızlandırmış oldu.
    Doğal olarak bu bir kod ve bellek yerleşimi meselesiydi; bir şeyler yer değiştirince CPU önbelleğine daha iyi oturmuştu.
    Elde edilen hız artışının kodun gerçekten “daha iyi” olmasından mı, yoksa bir yerlerde daha iyi hizalamayı şans eseri yakalamaktan mı kaynaklandığını bilmek gerçekten zor.
    Casey Muratori Substack’te bu konuda çok ilginç bir seri yazıyor.

    • Böyle bir linker piyangosunun %15’e varan iyileşme yaratabilmesi şaşırtıcı. Hangi durumlarda bu kadar büyük iyileşmeler ortaya çıkıyor, nadir mi, sonunda nasıl karar verdiniz merak ediyorum.
    • Derleyici kararlarını bilerek rastgeleleştirip kodun gerçekte ne kadar iyi çalıştığını daha kararlı biçimde tahmin etmeyi ve linker piyangosunu kazanma ya da kaybetme sonucundan daha az etkilenmeyi amaçlayan bir benchmarking projesi olduğunu belli belirsiz hatırlıyorum.
    • Uzun süre Java “performans mühendisi” olarak çalışan Aleksey Shipilёv, benchmarking’in zorlukları hakkında çok sayıda yazı ve sunum bıraktı. Blog yazılarını ya da sunumlarını şiddetle öneririm.
  • Gerçek durumu kurcalayıp ortaya çıkaran yazara övgüler. Python 3.14’ün tail call interpreter’ı hâlâ iyi bir iyileştirme ve dil çalışma zamanlarında birkaç puanlık artış zor kazanılan bir başarıdır.
    Sadece, sihirli bir %15 bedava öğle yemeği değildi.
    Daha önemlisi, bu olay benchmarking’de titizliğin ve birden fazla ortamda test etmenin önemini iyi gösterdi. Herkese fayda sağlayabilecek bir derleyici hatasını da ortaya çıkardı.
    Bir sonraki büyük performans artışı iddiasını yeniden kontrol ettirecek türden bir derin analiz. Üzerinde düşünülmesi gereken diğer soru şu: Ortalıkta dolaşan sayısız “%X daha hızlı” sonucunun ne kadarı aslında benchmark yapay etkisi ya da bilinmeyen regresyonlardan kaynaklanıyor?
    Gelecekte bu tuzaklardan daha iyi kaçınmak için ne yapmalıyız?

    • Daha büyük soru, kusurlu bir derleyici özelliği devreye girdiğinde Python performansındaki %10 düşüşün neden tespit edilmediği.
      Derleyicinin kendisi benchmark edilmiyor mu? Derleyici tarafındaki ya da Python tarafındaki mevcut benchmark’lar o derleyiciyi kullanmıyor muydu?
  • Bu, C’nin “makineye yakın” ya da “taşınabilir assembly” olduğu sözlerinin ne kadar isabetsiz olduğunu gösteren iyi bir örnek. Modern optimize ediciler, gözlemlenebilir bir etki yoksa mantığı cesurca değiştiriverir.
    Yazıda da “clang-19, computed-goto interpreter’ı sonuç ikilisinin beklenen tüm değerleri üretmesi anlamında ‘doğru’ derliyor; ama aynı zamanda bu çıktı, optimizasyon niyetiyle tamamen ters düşüyor. Üstelik diğer derleyici sürümleri ‘saf’ switch() tabanlı interpreter’a optimizasyon uygulayarak, bizim kaynak kodu yeniden yazarak ‘amaçladığımız’ şeyle tam olarak aynı optimizasyonu gerçekleştiriyor” deniyordu.

    • Diğer 80’ler-90’lar sistem programlama dilleri perspektifinden bakınca C hâlâ epey taşınabilir assembly sayılır.
      C’deki a += 1 ifadesinin sayısal değeri artırdığına güvenebilirsiniz; oysa C++’ta aynı ifade bellek ayırabilir, çağrı yığınını açabilir ya da bilinmeyen şeyler yapabilir. Benzer şekilde a = "a" C’de basit bir pointer atamasıyken C++’ta bellek ayırma vb. gerçekleşebilir.
      “C taşınabilir assembly’dir” ifadesi, her cümlenin doğrudan eşdeğer makine koduna derlendiği anlamına gelmez.
    • “Gözlemlenebilir etki yok” ifadesi 10 bin kelimelik bir blog yazısına uzamış haliyle karşımızda.
  • Derleyicinin döngü yapısını kurcalamasıyla tail call interpreter’ın tamamının duyurulduğu kadar etkili olmaması şaşırtıcı değil.

    1. CPU mimarisi ve sürümü çok önemli. Meselenin %95’i, branch predictor’ın en iyi şekilde çalışması için komut dispatch kodunu yerleştirmekten ibaret; C ise zaten böyle şeyleri desteklemek için yapılmış bir dil değil.
    2. C soyut makinesi de niyeti düzgün ifade edecek kadar düşük seviyeli değil. Her uygulama belirli bir derleyicinin ve belirli bir sürümün özelliklerine aşırı duyarlı hâle geliyor.
      Paranoyak interpreter uygulamaları yeniden doğrudan assembly yazmaya da dönebiliyor. LuaJIT, mimariler arasında taşınabilir kılmak üzere çok verimli bir assembly döngüsü uygulaması için bir makro sistemi geliştirmesiyle ünlü. Bu yüzden böyle şeylerle uğraşmak da eğlenceli.
      Birkaç yıl önce popüler interpreter döngüsü uygulama yöntemleri üzerine bir yazı ve testler de hazırlamıştım:
      https://github.com/vkazanov/bytecode-interpreters-post
    • Yazar olarak, “meselenin %95’i branch predictor’ın en iyi şekilde çalışması için komut dispatch kodunu yerleştirmektir” sözünün artık doğru olmadığını bu yazıyı yazarken öğrendim.
      Modern branch predictor’lar, çalışma aralığı yeterince uzunsa ve yorumlanan kodun kendi davranışı kararlıysa tek bir dolaylı jump’ı bile neredeyse tam doğru tahmin edebiliyor.
      Bunu hem gerçek donanımda hem de belirli simülasyon branch predictor’larında inceleyen bir makale var: https://inria.hal.science/hal-01100647/document
      Bu projede yaptığım deneyler de anekdot düzeyinde aynı sonucu destekliyor. Yazıya koyamadım ama donanım CPU sayaçları ve perf stat ile birkaç interpreter’a baktım; branch yanlış tahminlerinin baskın faktör olarak görünmediğini gördüm.
  • Python derleme performansını değerlendirmek son derece zor. Çünkü performansı iyileştirebilecek çok fazla build tekniği var.
    Yakın zamanda astral tarafı da conda-forge derlemesinin çoğundan belirgin biçimde daha hızlı olduğunu gösterirken bu sorunla karşılaştı:
    https://github.com/astral-sh/python-build-standalone/pull/54...
    Tail call interpreter’ın mevcut diğer build optimizasyonlarıyla birlikte kullanıldığında nasıl davrandığını merak ediyorum.

  • İlgili tartışmalar:
    https://docs.python.org/3.14/whatsnew/3.14.html#whatsnew314-... --> https://news.ycombinator.com/item?id=42999672 (66 puan | 25 gün önce | 22 yorum)
    https://blog.reverberate.org/2025/02/10/tail-call-updates.ht... --> https://news.ycombinator.com/item?id=43076088 (124 puan | 18 gün önce | 92 yorum)

  • Güzel yazı. Bir ayrıntı gözüme çarptı
    Referans verilen yazılardan biri olan https://simonwillison.net/2025/Feb/13/python-3140a5/ içinde “3.14.0a5, benchmark’ta 3.13’ten 1,12 kat hızlıydı; aşırı yük altındaki M2 MacBook Pro’mda” diye yazmış
    Bu kısım epey kafa karıştırıcı. Bilgisayar başka süreçler yüzünden aşırı yük altındayken benchmark çalıştırdığı anlamına mı geliyor? Öyleyse sonuç tamamen güvenilmez olmaz mı?
    Bu tür benchmark’ların dış değişkenleri ortadan kaldırmak için çok kontrollü ortamlarda yapıldığını sanıyordum

  • Burada bazıları %10’u “büyük”, %1’i “normal” diye niteliyor ama çift özyinelemeli Fibonacci’de kısmi inline etme gibi bir optimizasyon gerçek iş yükünü ve süreyi üstel olarak azaltabilir
    İki basamaklı argümanlarda 10 kattan fazla, yani binlerce yüzde bile mümkün. Kesin konuşmak gerekirse bu, problem boyutuna değil özyineleme derinliği farkına göre üstel [1]
    C derleyicileri de kod inline etme metriklerine karşı çok hassas davranabildiğinden, bu muazzam hızlanmanın gerçekten ortaya çıkıp çıkmaması kodun biçimine çok duyarlı olabilir
    Bu yüzden sorunun bir kısmı CPU’ların çok incelikli ve karmaşık hale gelmiş olması; ama diğer bir yönü de -O0 veya -O1 ötesindeki derleyicilerin de incelikli ve karmaşık hale gelmiş olması
    Bu yazı iyi ve okumaya değer, ama iki karmaşık şey etkileşime girdiğinde çok şaşırtıcı sonuçlar çıkabileceğinin sayısız örneklerinden biri de. Bu, bilişimin dışında da geçerli
    İnsanlar bu ders kaç kez tekrarlanırsa tekrarlansın aşırı basitleştirme eğiliminde
    Ayrıca yazıda en azından Intel ve Apple M1 olmak üzere iki CPU, gcc ve clang olmak üzere iki derleyici kullanılıyor; fakat gerçek dağıtım ortamlarında çok daha fazla Intel, AMD, ARM nesli ve uygulaması ile başka derleyiciler bulunabilir. Toplam karmaşıklığın yalnızca çok küçük bir kısmı örneklenmiş oluyor
    Daha bilimsel yapmak için, özellikle “1,01 kat” gibi farklarda zaman ölçümlerinde bir tür hata çubuğu olmalı. Ortalamanın standart sapması ya da bu durumda belki minimumun standart sapması daha iyi olabilir [2]
    Ölçüm hatasını azaltmak için muhtemelen işletim sisteminde CPU çekirdeği sabitlemeli zamanlama da gerekir
    [1] https://stackoverflow.com/questions/360748/computational-com...
    [2] https://github.com/c-blake/bu/blob/main/doc/tim.md

  • Yakın zamanda Python 3.9’dan 3.13’e kadar benchmark yaptım; 3.11’e kadar sürekli iyileşiyordu
    Ama Python 3.12 ve 3.13, 3.11’den yaklaşık %10 daha yavaştı
    Kendi yazdığım benchmark’ın yeterince iyi olmadığını düşündüm ama yine de çekirdek servislere dağıttım ve topladığım metriklerde de aynı değişim göründü
    Aynı sorunu yaşayan var mı?