Keberhasilan dan kegagalan Ninja (2020)
(neugierig.org)- Ninja, yang dirilis pada 2011, berawal sebagai sistem build mirip Make dan kemudian menjadi contoh sukses open source yang representatif, menyebar ke Chrome, sebagian Android, Meson, hingga proyek berbasis CMake
- Faktor yang menentukan keberhasilan dan kegagalannya bukan terutama kode itu sendiri, melainkan arsitektur, dan lebih jauh lagi masalah sosial seperti pemeliharaan serta ekspektasi pengguna
- Ninja membaca perintah, input, dan output yang tertulis di
ninja.build, memeriksa waktu modifikasi file, lalu menjalankan pekerjaan yang diperlukan secara paralel, dengan fokus membuat build inkremental untuk proyek besar menjadi cepat - Pilihan desain terbesarnya adalah tidak memuat sendiri fitur build tingkat tinggi, melainkan tetap menjadi eksekutor graf aksi (action graph), dengan keputusan yang kompleks dipisahkan ke generator
- Integrasi CMake, dukungan Windows, dan default eksekusi paralel memperbesar adopsinya, tetapi permintaan dan kontribusi yang bertentangan dengan tujuan desain tetap menjadi beban besar dalam pemeliharaan open source
Konteks mengapa Ninja digunakan luas
- Ninja adalah sistem build mirip Make yang dirilis sekitar 9 tahun sebelumnya; awalnya hanya proyek sampingan yang dibagikan dengan agak malu-malu, tetapi kemudian digunakan secara luas
- Kasus penggunaan utamanya sebagai berikut
- Chrome pada akhirnya menghapus semua build non-Ninja
- Android menggunakan Ninja untuk sebagian komponen besar sistemnya
- Proyek Meson menggunakan Ninja, dan tampaknya menjadi sistem build yang makin banyak dipakai di dunia perangkat lunak bebas
- Banyak proyek juga memakai Ninja bersama CMake, dan panduan build Swift juga mewajibkan pemasangan Ninja
- Ninja dirilis pada 2011, kepemilikan proyek berpindah tangan pada 2014, lalu kemudian diteruskan lagi ke maintainer utama ketiga
- Pengalaman inti yang terlihat dalam retrospektif ini adalah bahwa arsitektur berpengaruh lebih besar daripada kode, dan isu sosial lebih berpengaruh daripada arsitektur
Cara kerja dasar untuk build cepat
- Hal yang dilakukan Ninja relatif sederhana
- Pengguna menulis perintah yang akan dijalankan, file yang dikonsumsi tiap perintah, dan file yang dihasilkan ke dalam file
ninja.build - Ninja membaca file ini dan memeriksa waktu modifikasi berbagai file
- Ninja menjalankan perintah yang diperlukan secara paralel agar semuanya berada dalam keadaan terbaru
- Pengguna menulis perintah yang akan dijalankan, file yang dikonsumsi tiap perintah, dan file yang dihasilkan ke dalam file
- Dibandingkan Make, bahasa input build-nya memiliki lebih sedikit fitur, dan strukturnya berfokus pada menjalankan sedikit fitur itu dengan sangat cepat
- Alur eksekusinya terbagi menjadi tiga tahap
- Parsing dan interpretasi file build
- Pemeriksaan waktu modifikasi file input
- Eksekusi perintah yang diperlukan
- Tujuannya adalah mencapai tahap ketiga secepat mungkin, bahkan pada proyek besar dengan lebih dari 100 ribu file input
- Sebagai contoh optimasi, Ninja memetakan path file input ke objek memori yang unik sedini mungkin, lalu menggunakan perbandingan pointer untuk membandingkan kesamaan path setelahnya
- Detail performa tingkat lebih rendah dibahas dalam bab Ninja di The Performance of Open Source Software
- Selama beberapa tahun, muncul juga implementasi ulang Ninja
- Implementasi 20% yang menarik memang mudah, tetapi 80% sisanya penuh detail, dan belum ada implementasi yang diketahui lebih cepat
Pilihan-pilihan penting dalam desain
-
Representasi graf
- Make tidak menangani dengan baik kasus ketika satu aturan build menghasilkan beberapa file
- Ninja menggunakan graf bipartit antara file dan perintah, bukan graf antarfile
- Node file menjadi edge yang masuk ke node perintah, lalu node perintah memiliki edge yang keluar ke file output
- Struktur ini menangkap bentuk build sebenarnya dengan lebih baik
- Jika salah satu input berubah, perintah menjadi usang, dan ketika perintah dijalankan, semua output diperbarui
- Sebuah file tertentu dapat memiliki paling banyak satu edge input
- Baris perintah itu sendiri juga dapat dipandang seperti input dari node perintah; jika flag baris perintah berubah, perintah dan output menjadi usang
-
deps log dan dependensi header C
- Untuk menangani dependensi header C secara akurat, Ninja harus mengonsumsi data dependensi tambahan yang dibuat oleh compiler C
- Setelah mempertimbangkan antara penggunaan database dan kesederhanaan, dibuatlah format representasi deps log
- Format ini cukup ringkas, tetapi masih keliru dalam beberapa hal penting
-
Desain end-to-end / crash-only
- Ninja bukan proses daemon yang terus berjalan, dan setiap kali dijalankan ia bekerja dari awal
- Pilihan ini adalah struktur yang dipengaruhi oleh end-to-end principle dan crash-only software
- Jika jalur eksekusi dari awal dibuat cepat, tidak perlu membuat jalur kode “online” yang terpisah
- Proyek yang dapat tinggal di memori pada akhirnya cenderung mengabaikan performa startup
-
Pemeriksaan status file
- Salah satu alasan orang mengharapkan tool build tinggal di memori adalah untuk meng-cache status file di disk
- Pada praktiknya, kernel sudah meng-cache informasi ini di memori, dan meng-cache ulang di user space tidak banyak menghemat
- Di Linux, mengambil status file sangat cepat, dan Ninja menanganinya dalam satu thread
- Bahkan pada mesin yang dianggap “cepat” menurut standar 10 tahun lalu,
statuntuk 30 ribu file dapat dilakukan dalam puluhan milidetik
-
Kompromi antara skala dan spesifikasi
- Sebagai aturan praktis, perluasan skala 2x bisa dicapai dengan optimasi, tetapi perluasan 10x membutuhkan re-arsitektur
- Ninja dirancang dengan berpusat pada build Chrome, yang saat itu memiliki sekitar 30 ribu langkah build
- Kini Ninja juga digunakan di lingkungan yang lebih kecil, tetapi dalam kasus seperti itu keunggulan kecepatannya mungkin tidak diperlukan
- Di lingkungan yang lebih besar seperti build Android, Ninja mulai membentur batas skala, dan mungkin diperlukan pendekatan lain
- Untuk eksekusi paralel, Ninja mengharuskan pengguna menyediakan informasi yang cukup, tetapi tidak memaksa pengguna mengetahui seluruh build secara lengkap
- Ada kompromi antara akurasi, kemudahan, dan performa; dalam keseluruhan ekosistem, tool yang mengorbankan sebagian akurasi demi kemudahan bisa saja menghasilkan hasil yang lebih benar daripada tool yang lebih akurat tetapi merepotkan
Pemisahan yang lahir dari metafora “assembler”
- Sistem build menyediakan berbagai fitur tingkat tinggi, dan cara tiap tool menjelaskan dirinya pun begitu luas sampai sulit dibandingkan satu sama lain
- Wawasan inti Ninja adalah bahwa fitur tingkat tinggi apa pun yang disediakan, pada akhirnya sistem build harus membentuk graf aksi untuk menjaga file tetap dalam keadaan terbaru
- Ninja hanya mengeksekusi graf aksi ini, dan menyerahkan kepada pengguna generator apa yang akan ditempatkan di atasnya
- Pemisahan dua program ini awalnya dibuat karena cocok untuk proyek Chrome, tetapi kemudian menjadi kontribusi terpenting Ninja
- Keuntungannya ada dua
- Ninja sendiri tetap sederhana dan cepat
- Pekerjaan mahal seperti melakukan glob pada
*.cdipindahkan ke sisi generator
- Berbeda dari sistem build lain yang menangani semua pekerjaan sekaligus, struktur Ninja membuat graf aksi yang telah dihitung disimpan sebagai snapshot di disk
- Hasilnya, graf aksi menjadi semacam cache di antara build
- Generator bisa setingkat tinggi apa pun yang diinginkan
- Misalnya, generator dapat melakukan glob pada seluruh source tree dan menemukan file yang namanya mengandung
testsebagai test
- Misalnya, generator dapat melakukan glob pada seluruh source tree dan menemukan file yang namanya mengandung
- Developer harus menentukan sendiri biaya apa yang ingin dibayar
- Jika generator melakukan glob ke seluruh disk, itu mungkin saja, tetapi alasan build menjadi lambat juga makin jelas
- Pemisahan antara generator dan graf aksi pada praktiknya tidak sederhana, dan di Ninja pun ada banyak detail tentang lapisan mana yang harus menangani pekerjaan apa
- Sistem build Xcode atau Visual Studio secara teori juga dapat melakukan pra-perhitungan dengan cara yang sama lalu membuat snapshot hasilnya, tetapi godaan untuk mencampur lapisan dianggap membuat hal itu tidak berjalan baik
- Make mencoba memasukkan semua fitur untuk programmer seperti globbing, ekspansi variabel, substring, dan fungsi; hasilnya adalah bahasa yang lemah untuk mengekspresikan semua fitur yang dibutuhkan, tetapi cukup kuat untuk menulis Makefile yang lambat
- Ninja berusaha menghindari arah seperti ini
Default mendominasi performa yang dirasakan
- Secara default, Ninja menjalankan perintah yang diinginkan secara paralel
- Make juga dapat menjalankan secara paralel dengan flag
-j, tetapi default-nya adalah eksekusi serial - Makefile mudah ditulis dalam keadaan yang tidak aman untuk eksekusi paralel karena dependensinya tidak dinyatakan dengan cukup
- Ninja selalu menjalankan secara paralel bahkan pada sistem single-core, sehingga kesalahan seperti ini muncul lebih awal
- Program yang cocok dengan Ninja biasanya juga menjadi aman untuk build paralel
- Ninja tidak memiliki sistem canggih untuk mendeteksi dependensi yang salah; strukturnya membuat build yang salah lebih sering terjadi sehingga masalahnya terungkap
- Karena pengguna sering lupa atau tidak tahu flag eksekusi paralel Make, satu default saja bisa membuat Ninja terasa “lebih dari dua kali lebih cepat” daripada Make dalam penggunaan nyata
- Optimasi yang tidak benar-benar dialami pengguna punya arti kecil
Metrik yang penting dalam kecepatan
- Performa sistem build memiliki beberapa arti
- Berapa lama waktu build penuh dari awal
- Berapa lama waktu yang dibutuhkan untuk mengubah satu file setelah build selesai lalu membangun ulang
- Ninja berfokus mempercepat loop edit-compile dalam build inkremental pada codebase besar
- Saat pertama membuat Ninja, pembuatnya berusaha mengejar kecepatan blaze, yaitu bazel, karena mengingatnya sebagai sangat cepat
- Jika dilihat belakangan, untuk metrik yang diperhatikan, blaze tidak terlalu cepat, dan karena berupa program Java, bahkan menampilkan bantuan pun lambat
- Alasan terobsesi pada build inkremental adalah karena waktu iterasi dianggap sangat memengaruhi kepuasan programmer
- Ninja digunakan dalam loop edit-compile, dan perbedaan antara 1 detik dan 4 detik itu penting
- Arti “cepat” sulit disampaikan kepada pengguna, dan manual Ninja memperingatkan bahwa pada proyek kecil efek kecepatannya mungkin hampir tidak terasa
- Namun “cepat” adalah ungkapan yang mudah dijual, sehingga pengguna aplikasi kecil yang frustrasi karena kekurangan fitur pun mencoba memakai Ninja
- Ninja berfokus pada rebuild inkremental, tetapi sebagian pengguna melaporkan bahwa performa build penuh juga membaik
- Karena Ninja hampir tidak melakukan apa-apa, ia menggunakan sedikit CPU selama build, sehingga CPU tidak terlalu banyak diambil dari pekerjaan build yang sebenarnya
- Output-nya juga sangat ringkas
- Build yang berhasil biasanya hanya menampilkan satu baris
- Sistem build lain banyak menampilkan berbagai tahap dan informasi timing, sehingga bisa terasa berat
- Struktur yang sedikit berbicara membuat Ninja terasa lebih “seperti tidak ada”
Integrasi CMake dan dukungan Windows yang memperbesar adopsi
-
CMake
- Ninja awalnya dibuat untuk digunakan bersama sistem build khusus Chrome
- Peter Collingbourne melakukan pekerjaan menghubungkan Ninja dengan sistem build CMake yang lebih banyak digunakan
- Integrasi ini dimulai untuk pekerjaan LLVM, dan harus menambahkan semantik baru bukan hanya di CMake tetapi juga di sisi Ninja
- Peter disebut sebagai orang yang paling berjasa atas keberhasilan Ninja di dunia nyata
- Setelah itu para pembuat CMake mengambil alih integrasi tersebut, tetapi tidak cukup merespons permintaan dan kekhawatiran
- Pengembangnya sendiri sampai sekarang belum pernah menggunakan CMake secara langsung
-
Windows
- Karena Chrome juga menargetkan Windows, Ninja juga dibuat berjalan di Windows
- Sebagian besar dukungan Windows ditulis oleh kontributor
- Secara teknis, dukungan Windows adalah kerepotan besar
- Eksekusi proses dan penangkapan output adalah jenis perbedaan yang mengharuskan mempelajari API khusus platform yang baru
- Desain Ninja bergantung pada sifat bahwa waktu modifikasi terakhir file yang di-cache kernel dapat diambil dengan cepat, tetapi ini tidak berlaku di Windows
- Meski begitu, Windows adalah platform dengan jumlah developer besar
- Untuk tool bagus di Linux, dorongan untuk membagikannya kuat, tetapi untuk tool Windows, dorongan untuk menjualnya kuat, sehingga relatif lebih sedikit tool yang tersedia bebas
- Saat itu memang mengejutkan bahwa banyak pengguna awal Ninja adalah pengguna Windows, tetapi karena jumlah developer Windows besar, bahkan sebagian kecil yang tertarik pun terlihat sebagai pengguna
Memahami pekerjaan terkait
- Karena Ninja pada awalnya dimulai seperti hack demo akhir pekan, ada penyesalan karena tidak cukup meneliti pekerjaan terkait sebelum membuatnya
- Saat membuat sesuatu, penting untuk benar-benar memahami ruang desainnya
- Istilah “action graph” bukan ekspresi yang digunakan saat membuat Ninja, melainkan diambil dari sistem build Google, blaze/bazel
- bazel menjelaskan bahwa graf target tingkat tinggi seperti “library” dan “binary” menghasilkan action graph yang berupa graf perintah
- Gagasan melihat teks baris perintah sebagai input seperti file adalah salah satu contoh komputasi inkremental
- Komputasi inkremental terhubung bukan hanya ke sistem build, tetapi juga ke inkrementalitas UI
- Blog Jane Street memiliki artikel pengantar Incremental, dan ini juga terhubung dengan cara membangun UI seperti React
- "Build Systems à la Carte" adalah makalah yang membahas komputasi inkremental dalam konteks sistem build, dan disebut sebagai bahan yang akan berguna seandainya sudah ada sebelum Ninja dibuat
Beban pemeliharaan open source
- Pengalaman sebagai maintainer open source tidak terlalu menyenangkan
- Perasaan terhadap proyek bercampur antara kebanggaan saat seseorang mengatakan hal baik dan kekecewaan yang lebih besar
- Setelah merilisnya secara gratis, kadang ada orang yang meminta dengan ramah, tetapi lebih sering pengguna yang marah mengajukan tuntutan, sementara ucapan terima kasih jarang datang
- Ada juga orang yang berulang kali mengancam akan melakukan fork jika tuntutannya tidak diterima
- Kontribusi yang masuk akal tetapi bertentangan dengan tujuan desain juga menjadi kesulitan lain
- Saat menolak kontribusi dari orang yang baik dan cerdas, ada keinginan membalas dengan penjelasan yang memadai, dan penjelasan itu sendiri melelahkan
- Ia mulai memprogram melalui perangkat lunak bebas dan menulis kode karena ingin membalas apa yang diterima, tetapi perangkat lunak bebas saat ini tampak bukan lagi berbagi di antara orang yang setara; ada arus di mana pengguna menganggap diri mereka pelanggan dan memperlakukan penulis sebagai pihak yang bisa mereka keluhkan kepada manajer
- Motivasi sekarang lebih dekat pada membuat kagum atau memenuhi harapan segelintir hacker yang dihormati daripada meraih keberhasilan luas
- Keberhasilan Ninja membuat banyak hal dapat dipelajari, tetapi ia merasa pembelajaran serupa mungkin bisa didapat dari keberhasilan yang lebih kecil
Maintainer dan kontributor
- Nico Weber adalah kolaborator yang hati-hati dan menjadi maintainer Ninja selama bertahun-tahun
- Jan Niklas Hasse mengambil alih proyek setelah Nico, dan tampaknya mengelolanya dengan baik
- Selain itu, banyak kontributor Ninja lain yang berpartisipasi dalam proyek ini
1 komentar
Pendapat di Hacker News
Ungkapan bahwa “pemrograman sering dibicarakan seolah-olah sekadar menulis kode, tetapi pada kenyataannya arsitektur lebih penting daripada kode, dan isu sosial menjadi lebih penting daripada arsitektur” dengan tepat merangkum pemikiran yang sudah lama ada di benak saya
Bab 1 buku itu berargumen bahwa “masalah utama dalam pekerjaan kita lebih bersifat sosiologis daripada teknis”, lalu membahas isu sosial dan politik seperti chemistry dan kohesi tim, “waktu fokus”, lingkungan kerja yang tenang, serta biaya turnover
[1] https://en.wikipedia.org/wiki/Peopleware:_Productive_Project...
Karena itu, menurut saya rekayasa perangkat lunak jauh lebih dekat ke ilmu sosial daripada yang diakui atau dirasa nyaman oleh banyak orang. Ilmu sosial lebih cair dan kurang dapat diprediksi dibanding ilmu alam, jadi tampaknya ada kecenderungan untuk menghindari bagian sosial atau menanganinya hanya dengan cara yang sangat primitif. Orang terobsesi pada detail atomik teknologi, tetapi produk yang dibuat tim justru terlalu sering berakhir buruk
“Organisasi yang merancang sistem mau tidak mau akan menghasilkan rancangan yang menyalin struktur komunikasi organisasi tersebut.” — Melvin E. Conway, How Do Committees Invent?
Hal ini juga dibahas saat itu:
The Success and Failure of Ninja - https://news.ycombinator.com/item?id=23157783 - Mei 2020, 38 komentar
Setelah kira-kira setahun, repost biasanya masih oke, dan tautan ke thread lama ditujukan bagi pembaca yang ingin tahu lebih jauh
Bagian yang mengatakan “Android memakai Ninja untuk suatu komponen besar dalam sistem, tetapi saya tidak pernah benar-benar memahaminya” terasa lucu
Ninja memang punya peran besar di AOSP. Sistem build awalnya memakai Makefile, tetapi cepat menjadi rumit karena soong, sistem build deklaratif kustom, serta migrasi Bazel yang gagal dan terhenti. Google membuat kati(https://github.com/google/kati), yang mengubah Makefile menjadi file build Ninja, dan hasilnya benar-benar sangat besar:
λ wc -l out/build-qssi.ninja3035442 out/build-qssi.ninjaProses beralih dari Makefile/soong ke Ninja menyakitkan dan memakan beberapa menit bahkan di mesin modern, tetapi setelah Ninja menerimanya, ia berjalan sangat cepat
Saya juga ragu apakah layak memperkenalkan alat lain hanya untuk menerjemahkan dari satu alat ke alat lain. Apalagi jika file Ninja bisa sebesar itu dan sulit dibaca manusia
Pada bagian “Saya percaya programmer merasakan latensi, dan meskipun mereka tidak menyadarinya, suasana hati mereka terpengaruh. Google baru-baru ini melakukan riset di bidang ini dan sedikit banyak mengonfirmasi keyakinan saya”, saya penasaran apakah riset latensi itu benar-benar sudah dipublikasikan
Ninja cukup populer di kalangan developer game
Kalimat “Windows masih merupakan platform raksasa dari sisi developer, dan para developer itu kelaparan akan alat” terasa menarik. Dari sudut pandang orang yang terutama mengembangkan di Windows, justru developer Linux yang hidup tanpa debugger bagus seperti Visual Studio atau profiler seperti Superluminal terasa kelaparan akan alat. Belakangan ini jarak antara kedua platform tampaknya makin mengecil, dan saya juga menyukai utilitas Rust yang umumnya berjalan lintas platform
Saya tidak bisa memikirkan interaksi dengan Visual Studio yang layak disebut “bagus”. Ada beberapa yang mungkin “nyaris oke”, tetapi debugging tidak termasuk di dalamnya. Namun di 2022 debugger-nya tidak lagi penuh bug, jadi mungkin itu yang dimaksud
Saya cukup mengenal UNIX karena sudah memakai berbagai variannya sejak mengenal Xenix pada 1993, tetapi hampir tidak memakai WSL selain untuk menjalankan container Linux Docker
Beberapa hal yang masih memakai Ninja sudah diganti ke samurai, dan membaik dalam segala aspek yang memungkinkan
Meski begitu, menurut saya arah sistem build semacam ini keliru. Yang saya inginkan dari sistem build adalah menghitung hash isi dari semua input transitif, lalu mengecek apakah hasilnya ada di registry
Namun semuanya sangat kompleks. Kalau hanya melihat build lokal, setahu saya SCons dan Waf juga memakai hash untuk mendeteksi perubahan
Saya membuat library Deno “TDAR”[1] yang mirip dan berjalan baik, tetapi butuh sedikit usaha untuk membungkus tool command-line yang berasumsi berjalan di filesystem yang dapat berubah, seolah-olah dipanggil sebagai fungsi murni
[1] Belum dipisahkan dari proyek induknya[2], tetapi pernah saya bahas di video YouTube ini: https://youtu.be/sty29o8sUKI
[2] Kalau tertarik dengan hal seperti ini, silakan dorong saya untuk membuka source-nya. togos zero zero at gee mail dot comb
Dan “bukan yang saya inginkan” bukan berarti “keliru”. Karena di dunia ini ada juga orang-orang dengan selera berbeda
Untuk memakai modul C++20 di CMake, Ninja diperlukan, jadi Ninja akan tetap ada cukup lama
Bagian yang paling menarik adalah kalimat bahwa “kita sering harus berkompromi antara ketepatan dan kemudahan atau performa, dan harus sadar memilih posisi di sepanjang spektrum itu”
Sebagian programmer melihat dinamika ini terlalu kaku, dan menganggap salah satu nilai sudah jelas harus diprioritaskan. Namun dalam praktiknya interaksinya cukup halus. Misalnya, sebuah tool yang mengorbankan sebagian ketepatan demi kemudahan bisa menghasilkan hasil yang lebih benar di ekosistem secara keseluruhan dibanding alternatif yang lebih tepat tetapi kurang nyaman. Itu terjadi jika para programmer akhirnya menghindari alternatif yang kedua
“Maafkan nama yang memalukan” — justru namanya bagus
P.S.: Jika ini diimplementasikan, bisa dibuat lebih cepat juga: https://github.com/ninja-build/ninja/issues/2157 Namun seperti dijelaskan dalam tulisan, tool ini sengaja tidak memiliki state, bahkan hint yang sangat kecil dari eksekusi sebelumnya pun tidak disimpan