2 poin oleh GN⁺ 2024-10-03 | 1 komentar | Bagikan ke WhatsApp
  • Dalam situasi kontensi tinggi, perbedaan implementasi Mutex terlihat jelas; pthread_mutex_t dari Cosmopolitan Libc menunjukkan waktu eksekusi lebih singkat dan penggunaan CPU lebih rendah dibanding implementasi utama di Windows dan Linux
  • Pada pengujian 24-core Threadripper 29070WX di Windows, Cosmopolitan 2,75 kali lebih cepat daripada Microsoft SRWLOCK dan menggunakan sumber daya CPU 18 kali lebih sedikit
  • Pada 96-core Threadripper Pro 7995WX di Linux, Cosmopolitan 3 kali lebih cepat daripada glibc dan 11 kali lebih cepat daripada musl libc, dengan selisih waktu CPU yang lebih besar lagi
  • Di MacOS M2 Ultra, Apple Libc unggul tipis, sementara Cosmopolitan menggunakan algoritme sederhana yang bergantung pada system call ulock XNU di lingkungan ARM
  • Dasar performanya adalah integrasi nsync dari Google; jalur cepat CAS, antrean penunggu, futex/ulock/WaitOnAddress(), pencegahan starvation, dan desain designated waker menjadi inti utamanya

Metode benchmark Mutex berkontensi

  • Pengujian membuat 30 thread, dan setiap thread menaikkan integer global yang sama, g_chores, sebanyak 100.000 kali
  • Setiap operasi increment dijalankan di critical section yang sangat kecil di antara pthread_mutex_lock() dan pthread_mutex_unlock()
  • Nilai diukur dalam mikrodetik dan dibagi menjadi tiga jenis waktu
    • wall time: waktu nyata yang dibutuhkan program untuk berjalan, termasuk overhead pembuatan thread dan join
    • user time: waktu CPU yang dikonsumsi di ruang pengguna
    • system time: waktu CPU yang dikonsumsi di kernel
  • Karena banyak thread berjalan paralel, jumlah user time dan system time bisa lebih besar daripada wall time
  • Dalam kondisi tanpa kontensi, perbedaan performa antarimplementasi umumnya kecil, tetapi dalam situasi kontensi, perbedaan desain Mutex terlihat besar

Windows: Cosmopolitan lebih cepat daripada SRWLOCK

  • Pengujian Windows dilakukan pada 24-core Threadripper 29070WX
  • MutexShootout dari Mark Waterman menilai SRWLOCK Windows sebagai implementasi terkuat dalam skenario kontensi tinggi
  • Dalam kondisi yang sama, pthread_mutex_t Cosmopolitan mencatat wall time lebih singkat dan penggunaan CPU lebih rendah daripada SRWLOCK
Implementasi wall time user time system time
Cosmopolitan pthread_mutex_t 148.940µs 328.125µs 62.500µs
Microsoft SRWLOCK 410.416µs 5.515.625µs 1.640.625µs
Microsoft CRITICAL_SECTION 949.187µs 7.937.500µs 5.078.125µs
MSVC 2022 std::mutex 991.750µs 12.156.250µs 4.031.250µs
spin lock 1.165.435µs 24.515.000µs 15.000µs
Cygwin pthread_mutex_t 9.780.803µs 1.937.000µs 6.156.000µs
  • Cosmopolitan Mutex 2,75 kali lebih cepat daripada Microsoft SRWLOCK dan menggunakan 18 kali lebih sedikit sumber daya CPU
  • Dibandingkan dengan Cygwin Mutex, yang menyediakan implementasi POSIX di Windows, Cosmopolitan 65 kali lebih cepat
  • Dalam use case ini, Cygwin Mutex bahkan menghasilkan hasil yang lebih lambat daripada spin lock

Linux: selisih waktu CPU lebih besar daripada wall time

  • Pengujian Linux dilakukan pada 96-core Threadripper Pro 7995WX
Implementasi wall time user time system time
Cosmopolitan pthread_mutex_t 36.905µs 44.511µs 23.492µs
glibc pthread_mutex_t 101.353µs 150.706µs 2.724.851µs
spin lock 202.423µs 4.694.749µs 2.000µs
Musl libc pthread_mutex_t 411.013µs 2.167.898µs 9.926.850µs
  • Cosmopolitan Mutex 3 kali lebih cepat daripada glibc dan 11 kali lebih cepat daripada musl libc
  • Berdasarkan waktu CPU, Cosmopolitan menggunakan 42 kali lebih sedikit daripada glibc dan 178 kali lebih sedikit daripada musl libc
  • Pada workload yang mengharuskan semua thread mengerjakan pekerjaan yang diserialisasi, Cosmopolitan bisa terlihat di htop seolah hanya satu core yang aktif
  • Dalam situasi yang sama, glibc dan musl libc dapat memenuhi penggunaan CPU secara signifikan, sehingga bebannya lebih besar saat menjalankan banyak pekerjaan di server yang sama

MacOS: Apple Libc unggul tipis

  • Pengujian MacOS dilakukan pada M2 Ultra
Implementasi wall time user time system time
Apple Libc 52.263µs 43.202µs 911.009µs
Cosmopolitan pthread_mutex_t 54.700µs 63.055µs 1.003.674µs
  • Di MacOS M2 ARM64, Apple Libc sedikit lebih cepat daripada Cosmopolitan Mutex
  • Implementasi Mutex umum Cosmopolitan tidak berjalan baik di platform ini
  • Di MacOS ARM, Cosmopolitan menggunakan algoritme yang lebih sederhana berdasarkan Futexes Are Tricky dari Ulrich Drepper
  • Cara ini menyerahkan sebagian besar pekerjaan berat kepada system call ulock XNU, sehingga pada akhirnya menghasilkan performa yang hampir sama dengan implementasi Apple

Dasar performa: integrasi nsync

  • Inti performa Cosmopolitan Mutex adalah integrasi library nsync dari Google
  • nsync adalah library dengan 371 star di GitHub, ditulis oleh Mike Burrows dari Google
  • Dalam proses integrasi Cosmopolitan, pekerjaan berikut dilakukan
    • Menemukan dan memperbaiki bug lama yang belum terdeteksi di fungsi unlock Mutex nsync
    • Mem-porting ke operasi atomik C11 di AARCH64 sehingga nsync Mutex berkontensi menjadi 30% lebih cepat daripada upstream nsync
    • Menulis ulang integrasi sistem seperti futex agar portabilitas runtime dimungkinkan
    • Membuatnya berjalan mulus dengan pembatalan thread POSIX

Cara kerja nsync

  • Untuk mendapatkan lock dengan cepat, nsync pertama-tama langsung mencoba CAS(compare and swap) secara optimistis
  • Jika gagal mendapatkan lock, thread pemanggil ditambahkan ke doubly linked list para penunggu
    • Setiap penunggu memiliki semafornya sendiri pada cache line terpisah dan independen
    • Thread yang masuk ke status menunggu tidak lagi menyentuh lock utama
    • Ini penting untuk mengurangi overhead komunikasi yang muncul ketika banyak core menyentuh cache line yang sama
    • Sebagai latar belakang terkait, ada tautan ke What Every Programmer Should Know About Memory dari Ulrich Drepper
  • nsync menggunakan futex milik sistem operasi untuk menidurkan thread
    • Di MacOS, futex disebut ulock
    • Di Windows, WaitOnAddress() berperan sebagai futex
    • Di antara OS yang didukung Cosmo, hanya NetBSD yang tidak memiliki futex; semafor POSIX diimplementasikan di ruang kernel dan membutuhkan file descriptor baru untuk setiap semafor
  • nsync menghindari starvation dengan konsep “long wait”
    • Jika seorang penunggu telah dibangunkan 30 kali tetapi selalu gagal memperoleh lock secara internal, sebuah bit ditambahkan ke lock agar thread yang belum menunggu tidak bisa memperoleh lock
    • Jika bit ini ada, CAS awal thread yang baru masuk akan gagal sampai antrean tunggu sampai batas tertentu dikosongkan
  • Use case yang berkontensi pada critical section kecil menjadi lebih cepat dengan konsep designated waker
    • Ketika sebuah thread bangun dan mencoba memperoleh lock, sebuah bit disetel pada lock utama
    • Di nsync, fungsi unlock bertanggung jawab membangunkan thread penunggu berikutnya
    • Berkat bit ini, thread yang sedang melakukan unlock tidak perlu membangunkan penunggu kedua ketika sudah ada thread yang terbangun
  • Kode sumber terkait ada di cosmopolitan/third_party/nsync/mu.c dan cosmopolitan/libc/intrin/pthread_mutex_lock.c

Layanan nyata dan kode verifikasi

  • Sebagai demo live yang menggunakan Cosmo Mutex, server http://ipv4.games/ dapat dilihat
  • Layanan ini berjalan di GCE VM 2-core dan sejauh ini telah bertahan dari DDoS botnet berskala hingga 49.131.669 IP
  • Berkat nsync, query SQL dapat dipindahkan ke thread latar belakang dan struktur tempat thread saling mengirim pesan dapat digunakan
  • Indikator status dapat dilihat di /statusz
  • Kode benchmark mengukur wall time dengan gettimeofday() dan mengukur user time serta system time dengan getrusage()
  • Pada akhirnya, kode memeriksa g_chores == THREADS * ITERATIONS untuk memverifikasi bahwa semua operasi increment telah dilakukan

Catatan saat melihat spin lock

  • Dalam kondisi tanpa kontensi, perbedaan antarimplementasi Mutex kecil, dan spin lock beberapa baris bisa saja lebih baik
  • Namun spin lock sebaiknya digunakan hanya ketika benar-benar tidak ada pilihan lain
  • Spin lock berguna di tempat yang sulit memakai cara yang lebih kompleks karena batasan level sangat rendah, seperti kernel
  • Spin lock juga dapat digunakan sebagai detail implementasi internal nsync lock
  • Jika melihat performa lock hanya dari wall time, spin lock bisa tampak bagus, jadi waktu CPU juga harus diperiksa bersama dengan getrusage()

1 komentar

 
GN⁺ 2024-10-03
Komentar Hacker News
  • Perbandingan dengan implementasi mutex baru memang selalu menarik, tetapi saya tidak suka metode benchmark ini. Ini tampak hampir seperti microbenchmark
    Orang yang benar-benar menerapkan lock cepat biasanya memakai program multithread besar sebagai sarana uji kinerja utama. Dalam beban kerja kompleks dengan panjang critical section, jumlah thread yang saling berebut, dan tingkat kontensi yang bervariasi, faktor yang membuat mutex cepat atau lambat tampaknya berubah-ubah
    Sebagai referensi, saya menulis fast lock untuk WebKit, menciptakan abstraksi ParkingLot untuk implementasi lock (juga dipakai di Rust dan Unreal Engine), dan dulu juga melakukan riset serta menulis makalah tentang fast lock untuk Java

    • Sebagai orang yang membuat aplikasi desktop, saya ingin menambahkan bahwa pada aplikasi dengan puluhan thread yang sering berjalan, saya ingin melihat angka performa untuk kasus kontensi yang tidak tinggi
      Sebagai programmer audio real-time, biaya untuk mengambil mutex yang belum terkunci sudah lebih penting bagi saya. Di aplikasi kami, situasi ini sangat mendominasi. Demikian juga, saya ingin tahu biaya operasi try-lock yang akan gagal, bukan saat N thread saling berebut
      Karena Cosmopolitan bersifat open source, saya bisa mengukurnya sendiri, tetapi tetap saja terasa kurang memuaskan
    • Saya juga memikirkan hal yang sama. Ada banyak jenis mutex, dan pada beban kerja tertentu ada yang lebih baik daripada yang lain. Saya teringat DistributedMutex dan SharedMutex (https://github.com/facebook/folly/blob/main/folly/synchroniz..., https://github.com/facebook/folly/blob/main/folly/SharedMute...)
      Sama seperti hash map, jarang ada satu hash map yang lebih baik untuk semua beban kerja yang mungkin
    • Gaya mutex seperti ini juga akan dipakai di PyMutex milik Python 3.13. Ada benchmark nyata yang menunjukkan seberapa jauh PyMutex lebih cepat dibanding PyThread_type_lock sebelum 3.13
    • Ini jelas sebuah microbenchmark, dan kemungkinan besar tidak mewakili performa umum. Halaman ini memberi standar yang bagus untuk praktik benchmarking sistem operasi. Hanya saja ini lebih condong ke ranah akademik: https://gernot-heiser.org/benchmarking-crimes.html
    • Benchmark tertentu itu justru kemungkinan lebih menyukai perilaku yang tidak diinginkan, misalnya ketidakadilan patologis. Scheduling optimalnya adalah menjalankan semua operasi increment milik thread pertama, lalu semua milik thread kedua, dan seterusnya, karena dengan begitu trafik antarprosesor jadi minimum
      Mutex yang tidur selama waktu tetap saat gagal mengambil lock (misalnya 100µs) hampir selalu akan menggumpalkan pekerjaan sehingga mendekati perilaku ini dan bisa “menang” di benchmark. Tetapi pada aplikasi nyata, jika ada sedikit saja kontensi, mutex seperti itu mengerikan
      Maksud saya bukan mutex ini buruk atau mutex pthread itu baik, melainkan microbenchmark tersebut tidak mengukur hal yang mampu memprediksi performa aplikasi nyata
  • Pada bagian “alasan Cosmopolitan Mutex bagus adalah karena memakai library nsync”, saya belum pernah mendengar nsync, tetapi Mike Burrows juga menulis implementasi mutex produksi milik Google: https://github.com/abseil/abseil-cpp/blob/master/absl/synchr...
    Jadi saya penasaran kenapa implementasi mutex ini tidak masuk benchmark. Dan jika di macOS ia mendelegasikan ke __ulock, sepertinya hal yang sama bisa dicapai dengan lebih sederhana hanya memakai fungsi anggota wait() dan notify_one() di library atomic milik libc++
    Dulu juga ada thread besar terkait perbaikan implementasi mutex Rust: https://github.com/rust-lang/rust/issues/93740#issuecomment-... yang menarik adalah detail cara kerja internal hampir semua implementasi mutex populer dibahas di sana

    • Saat saya masuk ke AV, Mike sudah seperti legenda. Ada kisah bahwa setiap kali mesin pencari perlu dibuat lebih cepat, dia akan datang, menulis ulang beberapa fungsi inti, lalu kembali ke pekerjaan asalnya
      Mungkin benar, tetapi saya tidak bisa memastikannya sendiri. Dia adalah engineer yang sangat cerdas dan sangat berfokus pada efisiensi. Hanya saja kami tidak menjalankannya lama di satu server
    • Burrows juga terlibat dalam transformasi Burrows-Wheeler, Bigtable, Dapper, Chubby, dan lainnya
    • Thread Rust itu pada akhirnya memang sampai ke sana, tetapi pada dasarnya itu tentang pekerjaan Mara, jadi buku beliau yang terbit pada Januari 2023 juga disebut
      Implementasi mutex Rust saat ini masuk awal tahun ini, dan di Linux mungkin tidak terlalu berbeda, tetapi di Windows dan Mac setahu saya itu pekerjaan baru
      Meski begitu, penjelasan Mara tentang internal implementasi lain tetap menarik, hanya saja sebaiknya periksa apakah informasinya sudah usang untuk konteks Anda
    • Alasan implementasi mutex Abseil tidak masuk benchmark mungkin karena itu implementasi C++, bukan C. Ini hanya tebakan
    • Sepertinya Mike Burrows juga menerima penghargaan ACM, dan tampaknya ada fotonya di sana
      https://awards.acm.org/award-recipients/burrows_9434147
  • Kalimat seperti “ini masih library C baru jadi masih ada bagian kasarnya, tapi peningkatannya terlalu cepat sampai-sampai mulai terasa seperti kelalaian profesional kalau tidak dipakai di production” terdengar cukup aneh. Saya sangat menghargai proyek Cosmopolitan, tetapi klaim superioritas yang terlalu dibesar-besarkan seperti ini biasanya merupakan tanda bahaya yang cukup buruk

    • Menurut saya, klaim Justine pada umumnya cenderung benar. Hanya saja, memakai ungkapan yang berlebihan dan terkesan pamer itu tampaknya memang gaya, atau mungkin kepribadiannya.
      Saya juga paham kalau bagi sebagian orang itu bisa terasa kasar. Dulu juga pernah muncul drama seperti itu di llamacpp
    • Justine tampak seperti orang yang sangat hebat dan kreatif, tetapi saya tidak ingin memakai libc yang “baru” dan “masih ada bagian kasarnya” di production.
      Dalam production, prioritas tertinggi bukanlah “menjadi jauh lebih baik dengan sangat cepat”, melainkan stabilitas, prediktabilitas, dan keandalan. Tentu saja performa juga penting. Kode yang lebih cepat bisa mengurangi infrastruktur sehingga lebih baik dari sisi biaya dan lingkungan. Tetapi cepat itu urutan terakhir
    • Kalau terlalu lama sendirian ngoding di depan komputer, mungkin karena kurangnya kontak sosial, semacam kesombongan bisa ikut muncul. Kalau tidak ada mekanisme yang menahan rasa penting diri sendiri atau pentingnya pekerjaan kita, pencapaian yang memang mengesankan bisa terlihat jauh lebih megah daripada pengakuan luas yang sebenarnya sudah didapat.
      Misalnya, APE terasa seperti hack yang sangat mengesankan, tetapi kritik seperti “jadi sekarang bukan cuma tidak aman di satu platform, melainkan bisa sekaligus tidak aman di banyak platform?” juga mungkin saja.
      Semakin lama berkecimpung di bidang teknologi, semakin terasa bahwa keuntungan timbal balik yang sempurna itu amat jarang, dan kebanyakan hal adalah trade-off yang selalu datang bersama sisi untung dan rugi
    • Setidaknya bagi saya, itu terlihat seperti lelucon
    • Saya jadi penasaran apakah Anda pernah mempertimbangkan bahwa selera humor Anda dan Justine mungkin memang berbeda. Saya juga tidak yakin memposting ini di sini akan membantu siapa pun
  • Ini memang agak menyimpang, tetapi sebagai pengembang game saya justru jadi menyukai mutex yang lambat untuk banyak pekerjaan debug di semua build pengembang. Misalnya punya nama/ID debug, melacak pemiliknya, melaporkan waktu yang dihabiskan untuk kontensi ke profiler, dan juga melaporkan perpindahan kepemilikan ke profiler.
    Game cenderung menyusun konkurensi dengan cara yang berbeda, dan pola untuk menghindari lock juga sudah berkembang. Tetapi pola seperti itu sulit digunakan dan mengharuskan programmer mengubah strukturnya. Sebagian besar kode berawal dari “tempelkan lock di sini dulu lalu coba lewat milestone”.
    Bahkan lock yang cepat pun bisa melambat secara tak terduga, dan kalau tadinya ada jaminan real-time, lock itu bisa merusaknya. Rata-ratanya mungkin cepat, tetapi tail latency tidak hilang. Saya tidak ingin jadi orang yang harus kembali melacak “kenapa game kita tersendat”, tetapi biasanya memang saya yang jadi orang itu.
    Jadi saya lebih memilih lock yang lambat. Maksud saya lock yang terlihat besar dan merah di profiler. Kalau kelihatan sedang menghantam kita, ya refactor saja lalu hilangkan.
    Saya tahu ini tuntutan yang sulit. Di production AAA, orang yang benar-benar bisa memakai profiler bisa dihitung dengan jari. Dari beberapa production yang pernah saya lihat, kondisinya selalu begitu.
    Maaf jadi curhat, tetapi saya tetap berharap riset tentang primitif konkurensi cepat dan algoritme semacam ini terus berlanjut

    • Sedikit lebih menyimpang lagi, ini salah satu alasan kenapa mengembangkan game dengan Rust itu menyenangkan.
      Dalam game, kalau memungkinkan kita sama sekali tidak menginginkan kontensi lock, dan dalam banyak kasus kita bisa membuktikan bahwa mengambil lock itu tidak perlu. Misalnya, setiap frame dibagi menjadi beberapa tahap, dan akses mutable ke resource bersama tertentu hanya dibutuhkan pada tahap tertentu. Contohnya update() sebelum render() atau hot reload aset.
      Dengan memakai scoped thread dan aturan peminjaman Rust, kita bisa menyusun arsitektur sedemikian rupa sehingga mutex sama sekali tidak diperlukan, dan kita bisa yakin bahwa saat nanti perubahan kode membuatnya dibutuhkan, compiler akan mengeluarkan error yang tegas.
      Kalau memungkinkan, selalu lebih baik menerima error kompilasi daripada spike di profiler
    • Sangat setuju. Fitur debugging seperti deteksi deadlock atau pemeriksaan keadaan internal sangat mudah menutup biayanya sendiri. Kalau lock diambil cukup sering sampai memengaruhi performa, berarti desainnya perlu ditinjau ulang. Berbagi state mutable antar-thread sebaiknya dihindari
  • Di satu sisi, keluarga Cosmo/APE/redbean terlihat benar-benar mengesankan, dan komentar pada tulisan terkait juga umumnya positif, hampir tidak ada yang membantah konsepnya sendiri. Namun di sisi lain, saya hampir tidak pernah mendengar ada orang lain yang benar-benar memakainya
    Tidak semua orang membagikan pekerjaan mereka secara besar-besaran, tetapi kalau sudah lewat beberapa tahun, rasanya saya setidaknya akan melihat beberapa tulisan retrospektif proyek. Semua penyebutan Cosmo/APE/redbean yang saya lihat berasal dari situs Justine
    Jadi saya penasaran. Apakah ada jebakan tersembunyi? Apakah ini alat yang melakukan sesuatu yang buruk demi mendapatkan hasil? Apakah ini semacam lelucon atau trolling ala tom7 yang tidak saya pahami karena saya tidak cukup paham compiler atau runtime? Atau memang ini benar-benar alat brilian yang belum tersebar luas?

  • APE bekerja dengan trik cerdik yang sewaktu-waktu bisa diblokir, dan di OpenBSD itu memang benar-benar sudah diblokir
    Kebanyakan orang yang membuat perangkat lunak lintas platform tidak menginginkan satu executable tunggal yang berjalan di semua platform, melainkan satu codebase tunggal yang bekerja dengan benar di setiap platform yang didukung
    Dari sudut pandang itu, bahasa seperti Go terasa menyenangkan karena jika menghindari CGO, Anda bisa cross-compile ke semua target. Tetapi sulap agar bisa dieksekusi dengan tiga cara milik APE, meskipun sangat cerdas, tidak memberi keyakinan bahwa itu akan bekerja selamanya, dan bagi kebanyakan orang juga hampir tidak memberi manfaat praktis
    Setiap platform punya persyaratan packaging dan penandatanganan masing-masing, jadi lebih baik compile terpisah untuk target tiap platform

  • Secara pribadi, cosmo dan ape memang terlihat sangat cerdas, tetapi kalau alat biasa sudah bekerja dengan baik, saya tidak butuh kecerdikan semacam ini dalam pekerjaan
    Misalnya jika Anda sudah bisa cross-compile proyek ke sistem operasi dan platform lain, atau sudah punya infrastruktur build untuk itu, tidak ada alasan untuk mencari solusi yang membuat satu binary tunggal yang berjalan di mana-mana
    Selain itu, APE memakai hack cerdik agar bisa berjalan di beberapa sistem operasi. Bagaimana kalau suatu saat hack itu rusak ketika format executable berevolusi? Bagaimana kalau tidak ada siapa pun yang punya waktu untuk memperbaiki APE agar mengikuti perubahan itu?
    Sebaliknya, alat-alat yang membosankan seperti gcc, clang, go, rust akan terus diperbarui dan tetap bekerja meski sistem operasi terus berubah dan berevolusi. Jadi saya tetap berada di sisi yang membosankan. Alasan saya tidak memikirkan hal yang cerdas adalah karena yang membosankan bagi saya ya sudah bekerja dengan baik

  • llamafile dari Mozilla memakai ini. Bobot model dan executable digabung menjadi satu agar bisa dijalankan di mana saja pada platform cosmo/ape, dan juga menyalakan server HTTP redbean untuk interaksi
    Ia juga bisa dijalankan tanpa bobot yang digabungkan dan membaca bobot dari filesystem. Ini mungkin cara termudah untuk membuat LLM lokal benar-benar bisa “unduh lalu langsung jalan”

  • Cosmopolitan selalu terasa seperti celah teknis yang menarik untuk bahan tulisan blog. Jenis hal yang hampir pasti bisa masuk halaman depan tempat seperti HN hanya dengan kecerdikan dan obsesi terhadap setup-nya
    Tetapi untuk dipakai sebagai teknologi dasar seperti libc, ini tampak terutama berguna untuk mainan yang menarik atau proyek pribadi kecil
    Dalam konteks itu, ketika ini diajukan sebagai alternatif serius untuk hal seperti glibc, musl, msvcrt, rasanya agak aneh. Ini hack yang sangat lucu, tetapi kalau saya menemukannya dalam sesuatu yang saya andalkan secara serius, saya mungkin akan cukup terkejut

  • Mozilla punya proyek Llamafile berbasis Cosmopolitan libc: https://github.com/Mozilla-Ocho/llamafile
    Di Hugging Face juga rutin diunggah model-model populer yang dikemas ulang dalam format itu: https://huggingface.co/models?search=llamafile
    Namun apakah itu punya kegunaan praktis lebih dari sekadar mencoba model kecil dengan cepat adalah persoalan lain

  • Kalau memang sebagus itu, saya jadi penasaran kenapa tidak semua library C mengadopsi trik yang sama
    Dugaan saya, trik-trik itu mungkin hanya selalu lebih cepat pada arsitektur tertentu, model CPU tertentu, beban kerja tertentu, atau pola akses tertentu. Jika diuji dengan benar pada berbagai beban kerja di semua perangkat keras yang didukung, mungkin keunggulan yang sama tidak muncul
    Atau bisa juga semantik API pthread yang ingin diimplementasikan Cosmopolitan sedikit berbeda, dan implementasi ini mungkin tidak sepenuhnya patuh ketat pada spesifikasi
    Sulit membayangkan para penulis libc tidak mengikuti riset terbaru tentang primitif sistem operasi

    • Proyek-proyek seperti itu punya puluhan prioritas selain satu API tertentu. Terobsesi pada satu API saja bukan cara yang baik untuk memakai waktu yang terbatas. Dan sebagai contoh tandingan, lihat saja malloc dan routine string di libc Linux pada umumnya
      malloc di glibc lumayan bisa dipakai, tetapi dalam kecepatan keseluruhan dan skalabilitas, ia mudah kalah dari alternatif yang lebih modern. Fragmentasinya parah dan memburuk seiring waktu, serta ada banyak parameter tuning seperti MALLOC_ARENA_MAX yang sangat memengaruhi beban kerja nyata. malloc musl benar-benar buruk di semua level dari sisi performa. Menggunakan allocator musl pada program multithread bisa merusak performa dengan sangat parah sampai nyaris terasa lalai
      Musl juga tidak punya routine perbandingan string yang dioptimalkan dengan SIMD. Anda akan terkejut kalau tahu berapa banyak siklus CPU yang dihabiskan untuk pekerjaan seperti ini pada program yang tidak sepele; ini jelas terlihat dalam profil nyata, dan memperbaikinya akan membuat hampir semua program menjadi lebih baik secara umum. Routine optimalisasi glibc bagus, tetapi tetap tampak masih bisa dibuat lebih cepat
      Hal-hal seperti ini bukanlah “optimalisasi yang hanya khusus untuk satu arsitektur dan tidak bisa digeneralisasi”. Khususnya dua area ini adalah area yang sudah banyak dieksplorasi dan dipahami, yang hampir pada semua beban kerja dapat memangkas waktu wall-clock 2–5x dan juga sangat meningkatkan pemanfaatan working set jangka panjang. Lalu kenapa tidak diadopsi? Seperti biasa, mungkin karena ada hal lain yang harus dikerjakan, atau karena ada prioritas yang saling bertentangan seperti pada musl, yang lebih mengutamakan kesederhanaan daripada performa puncak
      Ini bukan menyalahkan proyek-proyek tersebut. Tidak ada yang berkata, “program saya sengaja dirancang agar sangat lambat dan tidak bisa melakukan apa pun dengan benar, dan saya bangga akan itu.” Hanya saja, gagasan bahwa para penggarap proyek itu merancangnya dengan memilih frontier Pareto yang sempurna sama sekali tidak realistis, dan tidak mencerminkan cara kebanyakan proyek benar-benar berjalan
    • Bisa jadi karena politik, sindrom NIH, atau maintainer lama
      Untuk mengubah sesuatu di glibc atau padanannya di dunia C++, butuh waktu selamanya
      Ada berbagai jenis primitif sinkronisasi, dan pthreads hanya mendukung sebagian di antaranya. Jika membatasi diri pada itu, biasanya artinya menukar performa demi portabilitas
    • Saya penasaran apakah kalimat “Sulit membayangkan para penulis libc tidak mengikuti riset terbaru tentang primitif sistem operasi” itu sindiran
      Saya tidak tahu soal maintainer libc, tetapi sebagai orang yang memelihara beberapa hal, saya tidak berusaha mengimplementasikan riset terbaru. Saya berusaha menjaga stabilitas dan memastikan performanya masih bisa diterima. Implementasi riset ada di luar anggaran “maintenance” saya
    • Saya penasaran apakah ada pertimbangan ABI dalam mengubah implementasi mutex pthread
    • Pertanyaan “Kalau memang sebagus itu, kenapa tidak semua library C mengadopsi trik yang sama?” mengingatkan saya pada lelucon ini
      Seorang pria dan seorang ahli statistik sedang berjalan di jalan lalu melihat uang kertas 50 euro. Ahli statistik itu terus berjalan, dan si pria berhenti lalu berkata, “Lihat, ada uang di lantai.” Ahli statistik menjawab, “Itu pasti palsu. Kalau asli, pasti sudah dipungut seseorang,” lalu terus berjalan. Pria yang satunya lagi mengambil uang itu
  • Thread dan mutex adalah hal yang paling membuat segala sesuatu menjadi rumit dalam ilmu komputer. Implementasi baru selalu saya pandang dengan skeptis sampai dipakai dalam skala besar selama bertahun-tahun
    Bug dalam mekanisme threading seperti ini sering lolos bahkan dari peninjauan paling ketat sekalipun. Saat Java muncul pada pertengahan 90-an, berbagai bug thread dan mutex di Solaris pun terungkap
    Yang dibutuhkan bukan implementasi mutex tercepat, melainkan implementasi yang bisa dipercaya

    • Mutex jauh dari hal yang paling “rumit”. Cara untuk mengimplementasikannya secara efisien juga tidak terlalu banyak. Dalam kebanyakan kasus, terutama di jalur baca, yang terbaik justru adalah menghindarinya
  • Kode ini tidak membenchmark performa lock mutex, melainkan kontensi mutex. Kalau Anda memakai lock seperti ini, kodenya perlu dievaluasi ulang
    Setiap kali tiap thread menambah g_chores, ia mengunci lalu membuka mutex. Ini menimbulkan overhead akuisisi dan pelepasan mutex yang sering, dan itu diulang 100.000 kali per thread
    Overhead ini menutupi perbedaan performa nyata antar mekanisme lock. Karena benchmark-nya didominasi kontensi lock, bukan pekerjaan nyata. Benchmark seperti ini tidak berguna

  • Saya penggemar Justine dan pekerjaannya, tetapi ini mungkin salah satu kasus uji benchmark mutex yang paling tidak menarik. Situasi ketika banyak thread terus-menerus menghantam mutex yang sama justru sesuatu yang sejak awal sebaiknya dihindari
    Jadi saya rasa tidak terlalu menarik mutex implementasi mana yang paling baik menangani kasus ini

    • Saya penasaran contoh kasus uji benchmark mutex yang menurut Anda bagus itu seperti apa
    • Dalam kebanyakan kasus saat saya memakai lock atau semaphore, itu berada di sekitar resource yang sangat mahal. Pemakaian resource itu jauh lebih dominan daripada overhead performa lock
    • Jadi apa yang seharusnya diukur? Kasus tanpa kontensi memang penting dan menjadi baseline, tetapi di luar itu, justru di sinilah kelemahan mutex. Jika tidak menangani kontensi dengan baik, perangkat keras jadi menganggur, kerja scheduler bertambah, atau masuk ke kernel jadi lebih sering
      Ada satu hal penting yang tadi terlewat: lock yang buruk performanya saat kontensi juga bisa menimbulkan efek sistemik yang sangat negatif, seperti menciptakan hotspot di jaringan memori, dan itu juga akan terlihat di sini
    • Saya tidak bisa sepenuhnya setuju dengan penilaian bahwa “banyak thread tidak seharusnya terus-menerus menghantam mutex yang sama”
      Saya bisa memikirkan beberapa kasus ketika banyak thread memang berkumpul pada mutex yang sama. Contoh sederhananya adalah pekerjaan mengisi struktur data seperti list atau dictionary secara bersamaan
      Itu memang bisa dilakukan dengan message passing, tetapi bisa memakai lebih banyak memori dan mungkin lebih lambat daripada menunggu untuk menulis ke lokasi bersama
  • Produksi bukan soal kecepatan, efisiensi, atau sekadar “hack cerdas” yang jelas-jelas pintar
    Jika saya harus mengorbankan 50% efisiensi demi jaminan bahwa saya tidak akan dipanggil untuk memperbaiki sistem yang rusak pada Minggu pukul 3 pagi, saya akan memilih itu setiap saat
    Produksi adalah soal keandalan, dan menulis kode yang andal itu 10 kali lebih sulit daripada menulis kode yang “cepat”