3 poin oleh GN⁺ 2025-03-11 | 1 komentar | Bagikan ke WhatsApp
  • Interpreter tail call baru di CPython awalnya tampak meningkatkan rata-rata pyperformance sebesar 10–15%, tetapi setelah baseline disesuaikan, peningkatan sebenarnya menyempit menjadi sekitar 1–5% tergantung konfigurasi
  • Peningkatan besar tersebut lebih mendekati hasil dari menghindari regresi LLVM 19 daripada efek implementasi baru semata; faktor utamanya adalah Clang 19 tidak mereplikasi dispatch computed goto lama dengan benar
  • Pada Intel Raptor Lake i5-13500, build clang19 1,09× lebih lambat daripada clang18 dan clang19.tc 1,03× lebih cepat, tetapi pada Apple M1 MacBook Air, clang19 1,12× lebih lambat dan clang19.tc tetap sekitar 1,00× lebih lambat
  • Karena batas tail duplication LLVM, jumlah indirect jump turun dari 332 pada clang18 menjadi 3 pada clang19, sehingga struktur yang dimaksudkan oleh interpreter berbasis computed goto praktis menghilang
  • Pendekatan tail call tetap merupakan peningkatan yang bermakna, tetapi untuk kode yang sensitif terhadap performa, pendekatan yang secara eksplisit meminta optimisasi compiler seperti musttail bisa lebih tangguh

Efek baseline yang tampak seperti peningkatan performa

  • Sekitar sebulan lalu, proyek CPython menggabungkan strategi implementasi baru untuk interpreter bytecode
  • Hasil awal menunjukkan peningkatan performa rata-rata 10–15% di berbagai platform dan benchmark pyperformance
  • Analisis berikutnya menunjukkan bahwa peningkatan besar ini terutama merupakan hasil dari secara tidak sengaja menghindari regresi LLVM 19
    • Jika dibandingkan dengan GCC, clang-18, atau LLVM 19 yang diberi flag tuning tertentu, peningkatannya turun menjadi sekitar 1–5%
  • Interpreter tail call itu sendiri memang menghasilkan peningkatan kecepatan nyata, tetapi besarnya peningkatan lebih moderat daripada yang diisyaratkan angka awal
  • Jika build dibuat dengan clang-19 atau versi setelahnya, jalur lama mungkin memang 10–15% lebih lambat
    • Simon Willison mereproduksi peningkatan kecepatan 10% saat membandingkan build python-build-standalone dengan Python 3.13

Konfigurasi benchmark dan angka utama

  • Beberapa build CPython dibandingkan di server Intel dan Apple M1 MacBook Air
    • Server Intel menggunakan Raptor Lake i5-13500 yang dioperasikan oleh Hetzner
    • Semua build menggunakan LTO dan PGO
    • Reproduksi build menggunakan konfigurasi nix
  • Target perbandingannya adalah sebagai berikut
    • clang18: Clang 18.1.8, computed goto
    • gcc: GCC 14.2.1, computed goto, hanya untuk Intel
    • clang19: Clang 19.1.7, computed goto
    • clang19.tc: Clang 19.1.7, interpreter tail call baru
    • clang19.taildup: Clang 19.1.7, computed goto dengan flag tuning -mllvm untuk menghindari regresi
  • Hasil rata-rata pyperformance dengan clang18 sebagai baseline adalah sebagai berikut
    • Raptor Lake i5-13500:
      • clang19: 1,09× lebih lambat
      • clang19.taildup: 1,01× lebih cepat
      • clang19.tc: 1,03× lebih cepat
      • gcc: 1,02× lebih cepat
    • Apple M1 MacBook Air:
      • clang19: 1,12× lebih lambat
      • clang19.taildup: 1,02× lebih lambat
      • clang19.tc: 1,00× lebih lambat
  • Interpreter tail call menunjukkan sebagian peningkatan kecepatan dibanding clang-18, tetapi lebih kecil daripada penurunan performa yang muncul saat berpindah ke clang-19
  • clang18.tc tidak dapat diukur
    • Karena interpreter tail call bergantung pada fitur compiler yang baru masuk di Clang 19
    • Karena batasan ini, dibutuhkan lebih banyak kombinasi benchmark untuk memahami situasinya

Keruntuhan dispatch akibat regresi LLVM 19

  • Interpreter bytecode tradisional memproses opcode dengan pernyataan switch di dalam loop while
    • Compiler biasanya mengompilasi switch menjadi jump table dan indirect jump
  • Sejak lama diketahui bahwa mereplikasi logika dispatch ke dalam setiap body opcode dapat membuat interpreter seperti ini lebih cepat
    • Alih-alih kembali ke awal loop di akhir opcode, logika untuk mendekode instruksi berikutnya dan mengindeks jump table ditempatkan secara terpisah pada setiap opcode
  • Compiler C menyediakan fitur untuk mengambil alamat label dan menggunakannya sebagai computed goto; sebelum pekerjaan tail call, CPython juga menggunakan loop interpreter dengan cara ini
  • Demi alasan performa compiler, Clang/LLVM secara internal menggabungkan beberapa goto dari computed goto menjadi satu indirectbr LLVM instruction
    • Setelah itu, pada tahap pembuatan kode, LLVM melakukan tail duplication untuk mereplikasi kembali logika percabangan ke setiap lokasi
    • Alur ini didokumentasikan pada level tinggi dalam artikel blog lama LLVM
  • LLVM 19 memperkenalkan batas pada tail duplication pass untuk menghindari kenaikan waktu kompilasi atau penggunaan memori yang parah dalam kasus tertentu
    • Pada CPython, batas ini membuat Clang membiarkan jump dispatch tetap dalam keadaan tergabung
    • Akibatnya, tujuan implementasi berbasis computed goto praktis menjadi tidak efektif
  • Masalah ini lebih dulu diidentifikasi pada implementasi bahasa lain dengan loop interpreter serupa, tetapi belum diketahui bahwa masalah itu juga memengaruhi CPython
  • Perbedaannya terlihat langsung jika menghitung jumlah indirect jump dengan membongkar object code
    • _PyEval_EvalFrameDefault pada build clang18: 332 jmp *
    • _PyEval_EvalFrameDefault pada build clang19: 3 jmp *

Posisi computed goto yang ambigu

  • Bahwa perubahan logika tail duplication menyebabkan regresi dikonfirmasi oleh fakta bahwa performa kembali ke level clang-18 setelah diperbaiki
  • Namun, besarnya regresi belum sepenuhnya terjelaskan
    • Secara historis, replikasi dispatch opcode pernah dikutip dapat mempercepat interpreter dari 20% hingga 100%
    • Pada prosesor modern dengan branch predictor yang lebih baik, riset yang lebih baru menunjukkan peningkatan yang lebih kecil, sekitar 2–4%
  • Python juga mendukung interpreter gaya lama yang menggunakan satu pernyataan switch sebagai opsi konfigurasi
    • clang18.nocg: 1,01× lebih cepat dibanding clang18
    • clang19.nocg: 1,02× lebih lambat dibanding clang18
    • clang19: 1,09× lebih lambat dibanding clang18
  • Fakta bahwa clang19.nocg lebih cepat daripada clang19 adalah pembalikan tambahan
    • Clang 18, atau Clang 19 dengan flag yang tepat, juga mereplikasi logika dispatch ke setiap body opcode pada interpreter berbasis switch
  • Perbedaan ini juga terlihat dari perbandingan jumlah indirect jump
    • clang18: 332
    • clang18.nocg: 306
    • clang19.nocg: 3
    • clang19: 3
  • Pada Clang modern, keseluruhan interpreter computed goto mungkin merupakan kompleksitas yang tidak perlu
    • Karena compiler dapat melakukan transformasi yang sama pada kode berbasis switch
    • Sebaliknya, computed goto sendiri juga tidak cukup untuk menjamin transformasi tersebut
  • GCC 14.2.1 tidak mereplikasi switch, tetapi saat computed goto digunakan, GCC mengimplementasikan perilaku yang dimaksud

Perbaikan dan cara menghindarinya

  • LLVM pull request 114990 digabung tak lama setelah artikel dipublikasikan dan memperbaiki regresi tersebut
  • Benchmark sebelum penggabungan juga mengonfirmasi bahwa perbaikan itu memulihkan performa yang diharapkan
  • Pada rilis sebelum perbaikan, ambang penghentian tail duplication dapat disesuaikan menggunakan opsi tuning yang ditambahkan oleh PR penyebab regresi
    • Jika batas tersebut disetel sangat besar pada clang-19, perilaku serupa dapat dipulihkan
  • Pada build LTO, penerusan opsi ini rumit
    • Tail duplication terjadi selama pembuatan kode, dan pada build LTO, pembuatan kode terjadi saat link time, bukan saat kompilasi
    • Karena itu, flag harus diteruskan bukan hanya ke compiler, tetapi juga ke lld
  • Contoh konfigurasi yang digunakan adalah meneruskan -mllvm -tail-dup-pred-size=5000 ke OPT dan LDFLAGS pada tahap ./configure

Masalah baseline yang terungkap oleh benchmarking

  • Benchmark dapat mengukur perbedaan performa antar-build tertentu secara akurat, tetapi untuk memperluas hasilnya menjadi “peningkatan performa umum”, diperlukan asumsi tambahan
  • Benchmark interpreter tail call menunjukkan hasil 10–15% lebih cepat dibanding interpreter computed goto lama, tetapi baselinenya terlalu kompleks untuk digeneralisasi menjadi kesimpulan yang lebih luas
  • Dalam pekerjaan performa, apa yang dijadikan baseline perbandingan adalah masalah yang berulang kali sulit
    • Walaupun pendekatan terbaik yang diketahui saat ini dipahami secara teoretis, menyesuaikan sistem operasi, opsi compiler, dan flag di dunia nyata adalah masalah terpisah
    • Benchmark publik yang berasal dari hardware lama atau skala yang sulit direproduksi mungkin tidak cocok untuk perbandingan langsung
  • Dalam makalah machine learning pun, saat mengklaim peningkatan algoritme, pertanyaan “dibandingkan dengan baseline apa?” sering kali lebih dulu penting daripada “apa yang dilakukan?”
  • Jika dibandingkan dengan baseline yang dituning dengan buruk, hasil yang impresif bisa mudah muncul

Compiler pengoptimasi dan musttail

  • Kasus computed goto menunjukkan bahwa ekspektasi terhadap compiler pengoptimasi bisa saling bertabrakan
    • Compiler harus menghormati maksud programmer dan mempertahankan perilaku yang sama
    • Pada saat yang sama, compiler juga harus melakukan transformasi yang rumit dan tidak intuitif untuk membuat kode lebih cepat
  • clang-19 mengompilasi interpreter computed goto dengan benar dari sudut pandang perilaku program, tetapi menghasilkan output yang sepenuhnya berbeda dari maksud optimisasinya
  • Versi Clang lain menerapkan optimisasi yang sama seperti yang dimaksudkan bahkan pada interpreter sederhana berbasis switch()
  • Computed goto pada level source code dan replikasi dispatch pada level machine code tampak hampir seperti konsep yang ortogonal
    • Karena hasil eksekusinya sama, alat yang ada saat ini sulit mengekspresikan perbedaan ini secara konsisten
  • Interpreter tail call berbasis pada atribut musttail
    • musttail tidak mengubah perilaku program yang secara tradisional dapat diamati, tetapi lebih mirip percakapan dengan optimizer
    • Compiler harus mampu melakukan optimisasi tertentu, dan jika optimisasi itu tidak terjadi, kompilasi diminta gagal
  • Pendekatan semacam ini bisa menjadi gaya penulisan kode yang lebih tangguh untuk kode sensitif performa saat compiler terus berevolusi
  • Mungkin juga layak dipertimbangkan apakah atribut hipotetis seperti [[clang::musttailduplicate]] dapat menggantikan computed goto pada loop while interpreter

Reproduksibilitas dan batasan yang disediakan nix

  • nix sangat membantu dalam mengelola beberapa build interpreter Python
    • Selama eksperimen, puluhan interpreter Python dibangun dan dibenchmark dengan empat compiler, yaitu gcc, clang-18, clang-19, clang-20, serta berbagai kombinasi flag
  • Dengan nix, versi paralel dapat dipertahankan secara reproducible dan terisolasi
    • Dapat dipastikan build tertentu berasal dari compiler dan flag yang mana
    • Definisi matriks build juga dapat dikelola dengan abstraksi singkat
  • Membangun LLVM kustom yang menerapkan patch perbaikan bug, lalu menggunakan compiler itu untuk membangun Python, juga dimungkinkan dengan sekitar 10 baris kode
  • Namun, masih ada kekurangan
    • nix memiliki beberapa perbedaan dari cara penggunaan software yang umum, sehingga sulit sepenuhnya menyingkirkan kemungkinan bahwa perbedaan ini memengaruhi benchmark atau kesimpulan
    • Misalnya, secara default nix membangun proyek dengan flag hardening tertentu, dan sejak awal ditemukan bahwa flag ini berdampak tidak proporsional pada interpreter tail call
  • Nix kuat dalam skalabilitas dan kustomisasi, tetapi menemukan cara kustomisasi tertentu membutuhkan banyak trial-and-error dan penelusuran source nixpkgs

1 komentar

 
GN⁺ 2025-03-11
Komentar Hacker News
  • Saya adalah penulis PR yang memasukkan interpreter tail-call ke CPython
    Pertama, terima kasih kepada Nelson yang menghabiskan hampir sebulan untuk menemukan penyebab masalah ini
    Saya, dan mungkin juga tim CPython, tidak pernah menduga bahwa compiler yang dipakai sebagai baseline memiliki bug seperti itu, dan saya sangat malu serta menyesal karena telah melakukan kesalahan besar
    Saya juga sudah memposting permintaan maaf: https://fidget-spinner.github.io/posts/apology-tail-call.htm...

    • Saat membaca kalimat “saya sangat malu dan menyesal karena ini kesalahan besar”, saya kira dia telah merusak performa CPython, padahal sama sekali bukan itu yang terjadi
      Yang diumumkan sebagai peningkatan performa 10~15% ternyata pada compiler tanpa bug lebih dekat ke 1~5%, dan angkanya juga bukan sepenuhnya salah, hanya benar dalam kondisi tertentu
      Dia sudah membuat peningkatannya, mengukurnya, dan PR-nya juga sudah direview, jadi bisa dibilang dia sudah melakukan bagiannya. Kebetulan versi clang yang dipakai untuk pengukuran menimbulkan salah paham pada angka tersebut, dan ini terlihat seperti kesalahan yang wajar yang bisa terjadi pada siapa saja
      Tetap saja, dia membawa peningkatan performa yang berarti dan juga menemukan regresi compiler, jadi angka yang keliru itu tampak kecil jika dibandingkan. Saya juga tidak yakin ada yang benar-benar dirugikan oleh kejadian ini, jadi rasanya sampai perlu minta maaf pun tidak
    • Sebagai catatan, setelah tulisan blog itu terbit, perbaikannya sudah di-merge ;)
      Jika peningkatan 3~5% bisa bertahan pada sistem lama seperti interpreter Python, itu sendiri sudah pencapaian besar dan sesuatu yang pantas dibanggakan
      Setelah sekitar 30 tahun, saya jadi cenderung curiga pada peningkatan performa yang berarti pada sistem yang sudah lama ada, terutama jika lebih dari 1%
      Peningkatan nyata memang ada, tetapi jarang, dan sering kali sebenarnya hanya memindahkan waktu ke tempat lain sehingga tidak tertangkap benchmark. Selain itu, benchmark dilakukan di lingkungan terkontrol untuk mengisolasi efeknya, sedangkan software nyata berjalan di VM atau desktop bersama banyak hal lain
      Saya sudah sering melihat perbaikan yang tampak jelas besar di lingkungan terisolasi, tetapi hilang atau malah menjadi negatif saat masuk ke lingkungan produksi
      CPython harus menargetkan banyak lingkungan sehingga lebih sulit lagi, dan juga tidak ada satu target produksi tunggal yang memungkinkan kita berkata “kalau tidak lebih cepat di produksi, berarti tidak benar-benar lebih cepat”. Meningkatkan performa di dunia seperti itu memang sangat sulit
      Pada akhirnya, tuning dan pengukuran performa itu sangat sulit, dan satu-satunya hal yang layak disesali mungkin hanyalah bahwa kita belajar fakta itu
      Saya harap dia tidak takut untuk salah. Bagaimanapun semua orang akan salah. Cukup lakukan seperti sekarang: bilang “sepertinya ini kesalahan kami”, lalu cari cara menanganinya dan cara menghindarinya ke depan
      [1] Ini juga sering terjadi bukan hanya pada performa, tetapi juga pada proses manusia. Misalnya tim alat code review bisa berkata “kami mengurangi waktu code review 15% dan mempercepat alur kerja semua orang”, padahal kenyataannya mereka menambah pekerjaan di bagian lain sistem, alur keseluruhan tidak jadi lebih cepat, dan 15% itu hanya dipindahkan ke tempat yang tidak diukur
    • Saya melihat salah satu motivasi utama dari desain interpreter tail-call adalah agar lebih tidak rentan terhadap perubahan mood optimizer. Demikian juga penjelasannya dalam tulisan asli tentang teknik ini (https://blog.reverberate.org/2021/04/21/musttail-efficient-i...)
      Secara teori, dengan control-flow graph dan profil seperti ini, compiler seharusnya punya cukup informasi untuk menghasilkan kode optimal bagi interpreter tradisional berbasis switch(). Tetapi dalam praktiknya, ketika fungsi sebesar dan sesaling terhubung seperti ini, kita jadi seperti harus bertarung dengan compiler
      Compiler men-spill variabel penting yang ingin dipertahankan di register, mengangkat manipulasi stack frame yang ingin didorong mendekati panggilan fungsi fallback, dan menggabungkan jalur kode identik yang tadinya ingin dipisahkan demi prediksi cabang. Rasanya seperti bermain piano sambil memakai sarung tangan
      Di sini juga terjadi tepat masalah “penggabungan jalur kode identik” itu, dan compiler yang “bermasalah” menggabungkan jalur identik tersebut sehingga performa memburuk
      Compiler yang “sudah diperbaiki” sekarang tidak lagi melakukan itu, tetapi perbaikan itu pada akhirnya juga lebih mirip penyesuaian heuristik internal compiler. Tidak ada jaminan compiler ini, atau compiler lain, akan terus mempertahankan heuristik dengan cara yang menguntungkan kita di masa depan
      Sebaliknya, interpreter tail-call memungkinkan kita mengekspresikan pola kode mesin yang diinginkan langsung di dalam interpreter itu sendiri. Dengan memakai atribut musttail, noinline, dan preserve_none bersama-sama, kita dapat membatasi masalah agar jauh lebih sedikit dipengaruhi heuristik optimizer
      Jadi keuntungan interpreter tail-call bukan sekadar peningkatan performa 3~5%, tetapi pada sebagian compiler bisa menjadi peningkatan performa yang andal yang bahkan lebih besar dari itu
    • Saya menghormati sikap yang bisa berkata, “maaf, saya salah.” Saya benar-benar tidak suka budaya berpura-pura keras kepala lalu bertingkah seolah berhasil, yang sekarang tampaknya jadi standar
    • Saya penasaran mengapa regresi performa baseline itu tidak muncul, atau apakah sebenarnya muncul, di halaman benchmark faster-cpython [0]
      Apakah benchmark bisa ditingkatkan untuk mencegah hal serupa?
      [0] https://github.com/faster-cpython/benchmarking-public
  • Benchmarking benar-benar sangat sulit dilakukan dengan benar. Terlalu banyak hal yang bisa menyesatkan orang
    Baru-baru ini saya pikir saya telah menemukan cara untuk membuat suatu algoritme sekitar 15% lebih cepat. Setidaknya semua benchmark mengatakan begitu
    Namun, bahkan ketika saya menyalin fungsi yang lebih cepat ke dalam test harness dan sama sekali tidak benar-benar memanggilnya, hanya versi lambat yang asli yang dipanggil, hasilnya tetap 15% lebih cepat. Kode yang bahkan tidak dijalankan ternyata membuat kode asli menjadi lebih cepat
    Tentu saja ini masalah tata letak kode dan memori; sesuatu bergeser dan jadi lebih pas dengan cache CPU
    Sangat sulit mengetahui apakah peningkatan kecepatan yang didapat memang karena kodenya benar-benar menjadi “lebih baik”, atau hanya karena secara kebetulan mendapatkan alignment yang lebih baik di suatu tempat
    Casey Muratori sedang menulis seri yang sangat menarik di Substack tentang topik seperti ini

    • Mengejutkan bahwa lotre linker seperti itu bisa menghasilkan peningkatan sampai 15%. Saya penasaran dalam kondisi seperti apa peningkatan sebesar itu bisa muncul, apakah ini jarang terjadi, dan akhirnya bagaimana Anda menilainya
    • Saya samar-samar ingat pernah ada proyek benchmarking yang dengan sengaja mengacak keputusan compiler, agar bisa memperkirakan dengan lebih stabil seberapa baik kode benar-benar berjalan dan tidak terlalu dipengaruhi oleh menang atau kalah dalam lotre linker
    • Aleksey Shipilёv, yang sudah lama berkecimpung sebagai “performance engineer” Java, telah menulis dan mempresentasikan banyak hal tentang sulitnya benchmarking. Saya sangat merekomendasikan tulisan blog dan presentasinya
  • Pujian untuk penulis yang menggali dan mengungkap keadaan sebenarnya. Tail-call interpreter di Python 3.14 tetap merupakan peningkatan yang bagus, dan peningkatan beberapa persen pada runtime bahasa adalah hasil yang sulit diraih
    Hanya saja, ini bukan makan siang gratis ajaib sebesar 15%
    Yang lebih penting, kejadian ini menunjukkan dengan sangat baik pentingnya ketelitian benchmarking dan pengujian di berbagai lingkungan. Ini juga mengungkap bug compiler yang pada akhirnya bisa bermanfaat bagi semua orang
    Ini adalah jenis analisis mendalam yang membuat saya ingin memeriksa ulang klaim peningkatan performa besar berikutnya. Pertanyaan lain yang patut dipikirkan adalah: dari begitu banyak hasil “X% lebih cepat” yang beredar sekarang, berapa banyak yang sebenarnya hanyalah artefak benchmark atau regresi yang belum diketahui?
    Bagaimana cara lebih baik menghindari jebakan seperti ini ke depannya?

    • Pertanyaan yang lebih besar adalah mengapa penurunan performa Python 10% tidak terdeteksi ketika fitur compiler yang cacat itu masuk
      Apakah compiler itu sendiri tidak dibenchmark? Atau benchmark yang sudah ada di sisi compiler maupun Python memang tidak menggunakan compiler tersebut?
  • Ini contoh yang bagus tentang betapa tidak tepatnya mengatakan bahwa C itu “dekat dengan mesin” atau “assembly portabel”. Optimizer modern akan dengan berani mengubah logika jika tidak ada efek yang bisa diamati
    Seperti yang dikatakan dalam tulisan itu, “clang-19 memang mengompilasi interpreter computed-goto secara ‘benar’ dalam arti biner hasilnya menghasilkan semua nilai yang diharapkan, tetapi pada saat yang sama output itu sepenuhnya menyimpang dari maksud optimisasinya. Selain itu, versi compiler lain menerapkan optimisasi pada interpreter berbasis switch() yang ‘naif’, sehingga persis mengimplementasikan optimisasi yang ‘dimaksudkan’ ketika kita menulis ulang kode sumbernya”

    • Dari sudut pandang bahasa pemrograman sistem lain era 80–90-an, C masih cukup dekat dengan assembly portabel
      Pada C, Anda bisa percaya bahwa a += 1 akan menambah nilai numerik, tetapi ekspresi yang sama di C++ bisa saja mengalokasikan memori, me-unwind call stack, atau melakukan hal lain yang tak diketahui. Demikian juga a = "a" di C hanyalah assignment pointer sederhana, sedangkan di C++ bisa melibatkan alokasi memori dan sebagainya
      Ungkapan “C adalah assembly portabel” bukan berarti setiap pernyataan langsung dikompilasi menjadi machine code yang setara
    • “Tidak ada efek yang bisa diamati” ternyata bisa berkembang menjadi tulisan blog 10 ribu kata
  • Tidak mengejutkan bahwa saat compiler mengutak-atik konstruksi loop, keseluruhan tail-call interpreter menjadi tidak seefektif yang diumumkan

    1. Arsitektur CPU dan versinya sangat penting. Dalam 95% kasus, masalahnya adalah menata kode dispatch instruksi agar branch predictor bekerja seoptimal mungkin, dan C pada dasarnya bukan bahasa yang dibuat untuk mendukung hal seperti ini
    2. Mesin abstrak C juga tidak cukup low-level untuk mengekspresikan niat dengan tepat. Implementasi apa pun akhirnya menjadi terlalu sensitif terhadap karakteristik compiler tertentu dan versi tertentu
      Implementasi interpreter yang paranoid kadang kembali menulis assembly secara langsung. LuaJIT terkenal karena mengimplementasikan sistem makro untuk membuat implementasi loop assembly yang sangat efisien tetap portabel lintas arsitektur. Itulah kenapa mengutak-atik hal seperti ini juga terasa menyenangkan
      Beberapa tahun lalu saya juga membuat tulisan dan pengujian tentang cara-cara implementasi loop interpreter yang populer:
      https://github.com/vkazanov/bytecode-interpreters-post
    • Sebagai penulis, saya justru belajar saat menulis artikel ini bahwa pernyataan “95% masalahnya adalah menata kode dispatch instruksi agar branch predictor bekerja optimal” sekarang sudah tidak lagi benar
      Branch predictor modern, jika rentang eksekusinya cukup panjang dan perilaku kode yang diinterpretasikan sendiri stabil, dapat memprediksi bahkan satu indirect jump dengan nyaris akurat
      Ada makalah yang meneliti hal ini baik pada perangkat keras nyata maupun branch predictor simulasi tertentu: https://inria.hal.science/hal-01100647/document
      Eksperimen yang dilakukan dalam proyek ini juga secara anekdotal mendukung kesimpulan yang sama. Ini tidak sempat dimasukkan ke artikel, tetapi saya melihat beberapa interpreter dengan hardware CPU counter dan perf stat, dan branch misprediction tidak tampak sebagai faktor yang dominan
  • Menilai performa build Python itu sangat sulit. Ada terlalu banyak teknik build yang dapat meningkatkan performa
    Baru-baru ini pihak astral juga mengalami masalah seperti ini, dengan menunjukkan bahwa build conda-forge terasa jauh lebih cepat daripada kebanyakan yang lain:
    https://github.com/astral-sh/python-build-standalone/pull/54...
    Saya penasaran bagaimana tail-call interpreter bekerja ketika dipakai bersama optimisasi build lain yang sudah ada

  • Diskusi terkait:
    https://docs.python.org/3.14/whatsnew/3.14.html#whatsnew314-... --> https://news.ycombinator.com/item?id=42999672 (66 poin | 25 hari lalu | 22 komentar)
    https://blog.reverberate.org/2025/02/10/tail-call-updates.ht... --> https://news.ycombinator.com/item?id=43076088 (124 poin | 18 hari lalu | 92 komentar)

  • Tulisan yang bagus. Ada satu detail yang menonjol
    Salah satu tulisan yang dirujuk, https://simonwillison.net/2025/Feb/13/python-3140a5/, menulis, “3.14.0a5 lebih cepat 1,12x daripada 3.13 dalam benchmark, di M2 MacBook Pro saya yang sangat kelebihan beban”
    Bagian ini cukup membingungkan. Apakah maksudnya benchmark dijalankan saat komputernya sedang terbebani oleh proses lain? Kalau begitu, bukankah hasilnya jadi sama sekali tidak bisa diandalkan?
    Saya kira benchmark seperti ini dilakukan di lingkungan yang sangat terkontrol untuk menghilangkan variabel eksternal

  • Di sini ada orang yang menganggap 10% itu “besar” dan 1% itu “normal”, tetapi optimasi seperti partial inlining pada Fibonacci rekursif ganda bisa mengurangi beban kerja dan waktu secara eksponensial
    Untuk argumen dua digit, peningkatannya bisa lebih dari 10x, yaitu ribuan persen. Tepatnya, ini eksponensial terhadap perbedaan kedalaman rekursi, bukan ukuran masalah [1]
    Compiler C juga bisa bereaksi sangat sensitif terhadap heuristik inlining kode, jadi apakah peningkatan kecepatan yang luar biasa itu benar-benar muncul bisa sangat bergantung pada bentuk kodenya
    Jadi, sebagian masalahnya adalah CPU telah menjadi sangat canggih dan kompleks, tetapi sisi lainnya adalah compiler, bahkan di luar -O0 atau -O1, juga telah menjadi canggih dan kompleks
    Tulisan ini bagus dan layak dibaca, tetapi ini juga salah satu dari banyak contoh bahwa ketika dua hal yang kompleks saling berinteraksi, hasil yang sangat mengejutkan bisa muncul. Ini juga benar di luar dunia komputasi
    Orang-orang punya kecenderungan kuat untuk terlalu menyederhanakan pelajaran ini, tidak peduli sudah berapa kali hal itu terulang
    Selain itu, tulisan tersebut setidaknya menggunakan dua CPU, Intel dan Apple M1, serta dua compiler, gcc dan clang, tetapi lingkungan deployment nyata bisa mencakup jauh lebih banyak generasi dan implementasi Intel, AMD, ARM, serta compiler lain. Jadi ini hanya mengambil sampel dari bagian yang sangat kecil dari keseluruhan kompleksitas
    Agar lebih ilmiah, terutama untuk perbedaan seperti “1,01x”, pengukuran waktu semestinya memiliki error bar dalam bentuk apa pun. Simpangan baku dari rata-rata, atau dalam kasus seperti ini mungkin simpangan baku dari nilai minimum akan lebih baik [2]
    Untuk mengurangi galat pengukuran, kemungkinan juga perlu penjadwalan dengan CPU core pinning dari OS
    [1] https://stackoverflow.com/questions/360748/computational-com...
    [2] https://github.com/c-blake/bu/blob/main/doc/tim.md

  • Saya baru-baru ini menjalankan benchmark Python 3.9 sampai 3.13, dan hasilnya terus membaik hingga 3.11
    Tetapi Python 3.12 dan 3.13 sekitar 10% lebih lambat daripada 3.11
    Saya sempat berpikir benchmark buatan saya sendiri mungkin tidak cukup bagus, tetapi tetap saya deploy ke layanan inti, dan metrik yang terkumpul juga menunjukkan perubahan yang sama
    Apakah ada orang lain yang mengalami masalah yang sama?