3 poin oleh GN⁺ 2025-03-11 | 1 komentar | Bagikan ke WhatsApp
  • Proyek CPython baru-baru ini memperkenalkan strategi implementasi baru untuk interpreter bytecode. Hasil awal menunjukkan peningkatan performa rata-rata 10-15% di berbagai platform
  • Namun, peningkatan performa ini terutama merupakan hasil dari upaya menghindari masalah regresi pada LLVM 19. Jika dibandingkan dengan tolok ukur yang lebih baik (misalnya GCC, clang-18, LLVM 19 dengan flag tuning tertentu), peningkatan performanya turun menjadi 1-5%

Hasil performa

  • Beberapa build interpreter CPython dibenchmark menggunakan berbagai compiler dan opsi konfigurasi. Pengujian dilakukan pada server Intel dan Apple M1 Macbook Air.
  • Semua build menggunakan LTO dan PGO. Dengan clang18 sebagai acuan, digunakan rata-rata yang dilaporkan oleh pypeformance/pyperf compare_to.
  • Perbandingan performa compiler
    • Apple M1 Macbook Air :
      • clang18: acuan
      • clang19: 1,12x lebih lambat
      • clang19.taildup: 1,02x lebih lambat
      • clang19.tc: 1,00x lebih lambat
      • gcc: N/A
  • Interpreter tail-call masih menunjukkan peningkatan kecepatan dibandingkan clang-18, tetapi penurunan kecepatan saat beralih ke clang-19 jauh lebih drastis.

Regresi LLVM

Latar belakang singkat

  • Interpreter bytecode tradisional tersusun dari pernyataan switch di dalam loop while. Sebagian besar compiler mengompilasi switch menjadi tabel lompatan.
  • Compiler C modern mendukung pola mengambil alamat label dan menggunakannya sebagai "computed goto". CPython menggunakan pola ini sampai pekerjaan tail-call dilakukan.

Regresi LLVM 19

  • LLVM 19 membatasi pass tail-duplication, sehingga duplikasi dihentikan ketika ukuran IR melebihi ambang tertentu. Akibatnya, di CPython semua lompatan dispatch digabungkan, sehingga tujuan dari implementasi berbasis computed goto sepenuhnya batal.

Keanehan tambahan

  • Ada keyakinan bahwa perubahan pada logika duplikasi tail-call menyebabkan regresi, tetapi besarnya regresi itu tidak bisa dijelaskan sepenuhnya.
  • Pada prosesor modern, peningkatan kecepatan 2-4% lebih umum terjadi.

Apakah computed goto diperlukan?

  • Benchmark clang19.nocg diklaim lebih cepat daripada clang19. Ini menunjukkan bahwa compiler dapat melakukan optimisasi yang sama dengan menggunakan interpreter berbasis switch.

Perbaikan

  • Pull request LLVM 114990 telah memperbaiki regresi tersebut. Perbaikan ini memulihkan performa yang diharapkan.

Refleksi

Tentang benchmarking

  • Saat mengoptimalkan sistem, benchmark dan metodologi benchmarking disusun untuk mengevaluasi perubahan yang diusulkan.
  • Benchmark membutuhkan lebih banyak asumsi dan keyakinan agar satu titik data tertentu bisa digeneralisasi.

Baseline

  • Saat mengusulkan solusi atau metode baru, praktik umum adalah membandingkannya dengan "pendekatan terbaik yang diketahui saat ini".

Tentang rekayasa perangkat lunak

  • Sistem perangkat lunak itu kompleks, saling terhubung, dan berubah dengan cepat.
  • Compiler pengoptimal berada dalam ketegangan antara menghormati niat programmer dan tetap mengoptimalkan kode.

Compiler pengoptimal

  • Atribut musttail mewakili jenis baru fitur compiler yang berkaitan dengan optimisasi. Ini dapat menyediakan gaya yang lebih kuat untuk menulis kode yang sensitif terhadap performa.

Satu hal lagi tentang nix

  • nix sangat berguna dalam proyek ini. Ini sangat membantu untuk mengelola dan membangun beberapa versi interpreter Python.

1 komentar

 
GN⁺ 2025-03-11
Opini Hacker News
  • Halo. Saya adalah penulis PR yang memperkenalkan interpreter tail-calling ke CPython

    • Pertama, saya ingin menyampaikan terima kasih kepada Nelson, yang menghabiskan hampir sebulan untuk mencari akar masalah ini
    • Saya juga merasa sangat malu dan menyesal telah membuat kesalahan sebesar ini
    • Tim CPython juga tidak menyangka compiler yang kami gunakan memiliki bug seperti ini
    • Saya telah memposting blog permintaan maaf di sini: tautan
  • Benchmarking memang pekerjaan yang sangat sulit dilakukan dengan baik

    • Baru-baru ini saya menemukan cara untuk membuat sebuah algoritme sekitar 15% lebih cepat
    • Namun saat pengujian, kode asli justru menjadi 15% lebih cepat bahkan tanpa memanggil versi fungsi yang lebih cepat
    • Ini adalah masalah tata letak kode dan memori, karena penyelarasan dengan cache CPU menjadi lebih baik
    • Casey Muratori sedang membuat serial yang menarik tentang topik seperti ini
  • Saya memuji penulis yang berhasil mengungkap kebenaran masalah ini

    • Interpreter tail-call di Python 3.14 tetap merupakan peningkatan yang baik
    • Insiden ini mengajarkan pentingnya ketelitian dalam benchmarking dan pengujian di berbagai lingkungan
    • Selain itu, kini sebuah bug compiler yang bisa menguntungkan semua orang telah ditemukan
    • Saya jadi bertanya-tanya berapa banyak hasil "X% lebih cepat" yang sebenarnya disebabkan oleh artefak benchmarking atau regresi yang belum diketahui
  • Ini adalah contoh yang baik bahwa C bukan bahasa yang "dekat dengan mesin"

    • clang-19 mengompilasi interpreter computed goto dengan "benar", tetapi menghasilkan output yang sepenuhnya berbeda dari maksud optimisasinya
    • Versi compiler lain juga menerapkan optimisasi pada interpreter berbasis switch() yang "naif"
  • Dengan menyesuaikan cara compiler mengatur loop, interpreter tail-call tidak seefektif yang diumumkan

    • Arsitektur dan versi CPU sangat penting
    • Mesin abstrak C tidak cukup low-level untuk mengekspresikan maksud dengan tepat
    • Beberapa implementasi interpreter yang sangat paranoid kembali menulis assembly secara langsung
    • luajit mengimplementasikan sistem makro agar implementasi loop assembly yang efisien bisa portabel antar arsitektur
  • Menilai performa build Python sangatlah sulit

    • Baru-baru ini tim astral menunjukkan bahwa build conda-forge lebih cepat daripada sebagian besar build lainnya
    • Saya penasaran bagaimana interpreter tail-call bekerja bersama optimisasi build lainnya
  • Diskusi terkait:

  • Artikel yang luar biasa

    • Salah satu artikel yang dirujuk menyebut bahwa 3.14.0a5 1,12 kali lebih cepat daripada 3.13
    • Saya bingung apakah benchmark dijalankan saat proses lain sedang membebani sistem
    • Benchmark harus dilakukan dalam lingkungan yang dikendalikan secara ketat untuk menghilangkan variabel eksternal
  • Baru-baru ini saya melakukan benchmarking dari Python 3.9 hingga 3.13

    • Hingga 3.11 performanya membaik, tetapi 3.12 dan 3.13 sekitar 10% lebih lambat daripada 3.11
    • Saya sempat berpikir benchmark saya sendiri tidak memadai, tetapi saat diterapkan ke layanan inti, saya juga mengamati perubahan yang sama
  • Saya penasaran bagaimana optimisasi seperti ini berkaitan dengan tail-call optimization

    • Implementasi jump table interpreter seharusnya tidak memengaruhi pembuatan stack frame