- Ketimpangan antara kinerja I/O dan kecepatan pemrosesan CPU yang belakangan dibahas diuji lewat eksperimen, dan ditunjukkan bahwa pada praktiknya CPU masih menjadi batas utama
- Kecepatan baca sekuensial mencapai 1.6GB/s pada cold cache dan 12.8GB/s pada warm cache, tetapi perhitungan frekuensi kata pada single-thread hanya berada di kisaran 278MB/s
- Struktur branch dalam kode menghambat vectorization, dan bahkan dengan optimasi sederhana pada konversi huruf kecil pun hanya meningkat menjadi sekitar 330MB/s
- Bahkan perintah
wc -w hanya mencapai 245MB/s, menegaskan bahwa komputasi CPU dan pemrosesan branch, bukan disk, adalah bottleneck
- Dengan vectorization manual berbasis AVX2, throughput berhasil dinaikkan hingga 1.45GB/s, tetapi itu masih hanya sekitar 11% dari kecepatan baca sekuensial, membuktikan bahwa bottleneck ada pada CPU, bukan I/O
Perbandingan kecepatan I/O dan kinerja CPU
- Mengikuti klaim Ben Hoyt, dilakukan eksperimen untuk melihat apakah peningkatan kecepatan baca sekuensial belakangan ini telah melampaui stagnasi kecepatan CPU
- Dengan metode pengukuran yang sama, tercatat 1.6GB/s pada cold cache dan 12.8GB/s pada warm cache
- Namun, saat menjalankan perhitungan frekuensi kata pada single-thread, throughput-nya hanya 278MB/s
- Ini berarti bahkan saat cache sudah hangat, kecepatannya hanya sekitar seperlima dari kecepatan baca disk
Eksperimen perhitungan frekuensi kata berbasis C
- Dengan GCC 12,
optimized.c dikompilasi menggunakan opsi -O3 -march=native lalu dijalankan pada file input 425MB
- Hasil: membutuhkan 1.525 detik, dengan throughput 278MB/s
- Banyak branch dan early exit dalam kode menghambat optimasi vectorization oleh compiler
- Setelah logika konversi huruf kecil dipindahkan ke luar loop, throughput naik menjadi 330MB/s
- Saat memakai Clang, vectorization berjalan lebih baik
Perbandingan dengan penghitung kata sederhana (wc -w)
- Menjalankan perintah
wc -w, yang hanya menghitung jumlah kata alih-alih frekuensinya
- Hasil: 245.2MB/s, lebih lambat dari perkiraan
wc menangani berbagai karakter spasi seperti ' ', '\n', '\t' serta karakter locale
- Karena itu beban komputasinya lebih besar dibanding kode yang hanya membedakan spasi sederhana
Upaya vectorization berbasis AVX2
- Dengan memanfaatkan fitur CPU modern, implementasi vectorization dibuat menggunakan instruction set AVX2
- Menggunakan register 256-bit, dengan data disejajarkan ke 32-bit
- Untuk membandingkan karakter spasi digunakan instruksi
VPCMPEQB
- Deteksi batas kata dilakukan memakai bit mask (PMOVMSKB) dan instruksi Find First Set (ffs)
- Pendekatannya terinspirasi dari implementasi
strlen di Cosmopolitan libc
Hasil performa dan kesimpulan
- Kode vectorization manual (
wc-avx2) mencapai throughput 1.45GB/s
- Diverifikasi menghasilkan nilai yang sama dengan
wc -w (82,113,300 kata)
- Bahkan pada kondisi cold cache, waktu komputasi di mode user tetap mendominasi
- Ini menegaskan bahwa bottleneck ada pada komputasi CPU, bukan disk I/O
- Secara keseluruhan, kecepatan disk sudah cukup tinggi, tetapi pemrosesan branch dan perhitungan hash di CPU tetap menjadi faktor pembatas
- Kode dan hasil eksperimen dipublikasikan di GitHub (
haampie/wc-avx2)
1 komentar
Komentar Hacker News
Saya berpikir bahwa batas performa CPU modern ditentukan oleh jumlah data yang bisa diproses satu inti, yakni kecepatan
memcpy()Sebagian besar inti x86 berada di kisaran 6GB/s, sedangkan seri Apple M di kisaran 20GB/s
Angka seperti ‘200GB/s’ yang disebut dalam iklan hanyalah bandwidth gabungan semua inti; satu inti sendiri masih tetap berada di sekitar 6GB/s
Jadi meskipun menulis parser yang sempurna, batas ini tetap tidak bisa dilampaui
Namun, dengan memakai format zero-copy, CPU bisa melewati data yang tidak perlu sehingga secara teori bisa ‘melampaui’ 6GB/s
Format Lite³ yang sedang saya kembangkan memanfaatkan prinsip ini dan menunjukkan performa hingga 120 kali lebih cepat daripada simdjson
Misalnya, Zen 1 menunjukkan 25GB/s pada satu inti(tautan referensi)
Menurut hasil microbenchmark yang saya tulis, Zen 2 mencapai 17GB/s tanpa AVX dan hingga 35GB/s dengan non-temporal AVX
Pada Apple M3 Max, terukur hingga 125GB/s dengan non-temporal NEON
Jadi angka 6GB/s untuk x86 dan 20GB/s untuk Apple jauh lebih rendah dari kondisi nyata
Karena iGPU bisa mengakses memori terpadu
Jadi untuk pekerjaan seperti penyalinan memori berukuran besar, parsing paralel, atau kompresi/dekompresi, secara teknis lebih menguntungkan memanfaatkan iGPU sebagai blitter
Namun, ‘melewati’ yang dimaksud dalam format zero-copy tetap terjadi pada satuan cache line
Sepertinya penulis asli salah menafsirkan keluaran perintah
timeWaktu
systemadalah waktu CPU yang dipakai kernel atas nama prosesDalam contoh itu, jika
real0.395s,user0.196s, dansys0.117s, maka CPU total hanya bekerja selama 313ms, dan 82ms sisanya berada dalam keadaan idleArtinya, memang bekerja lebih cepat daripada subsistem disk, tetapi selisihnya tidak besar
Selain itu, jalur I/O berada dalam kondisi CPU-bound — bahkan jika disk dan kodenya tak terbatas cepat, eksekusi kode I/O kernel tetap membutuhkan 117ms
Saya penulis artikel itu. Ada lanjutan: I/O is no longer the bottleneck, part 2
Tulisan analisis yang membahas berbagai teknik optimasi yang dipakai peserta menarik untuk dibaca
Pendekatannya berbeda tergantung kompleksitas masalah dan jumlah klasifikasi karakter spasi
Bottleneck performa bukan selalu satu faktor tunggal seperti “CPU atau I/O”, melainkan sumber daya yang pertama kali jenuh dalam workload nyata
Bisa CPU, bandwidth memori, cache, disk, jaringan, lock, latensi, dan lain-lain
Karena itu, harus diukur, dibuktikan lewat profiling, lalu diukur lagi setelah perubahan
Masalahnya bukan CPU atau I/O, melainkan keseimbangan antara latensi dan throughput
Sebagian besar perangkat lunak lambat karena mengabaikan latensi
Jika data ditata secara linear di memori, atau diterapkan batch processing dan paralelisasi, hasilnya bisa jauh lebih cepat
Saya membayangkan arsitektur yang hanya terdiri dari CPU ↔ cache ↔ penyimpanan non-volatil
Jika
mmap()punya karakteristik performa yang sama denganmalloc(), program mungkin bisa menetapkan nama file sebagai memori dan menyerahkan persistensi kepada OSBanyak desain perangkat lunak masih terikat pada keterbatasan era hard disk
fsync()tetap lambatUntuk persistensi yang sesungguhnya, pendekatan lain tetap diperlukan terlepas dari apakah medianya disk berputar atau bukan
Faktanya, sebagian besar permintaan memori memang terjadi lewat
mmap()Hanya saja, kernel sulit memprediksi pola akses sehingga bisa lebih lambat daripada
read/writeDi lingkungan cloud, performa kadang juga menjadi alat untuk mengatur harga paket
Performa hardware berkembang secara luar biasa, tetapi sebagian perangkat lunak (terutama Windows atau aplikasi messenger) justru terasa makin lambat
Tidak efisien untuk workstation jarak jauh bagi developer
Telegram atau FB Messenger cepat, tetapi Teams atau Skype tidak
Beberapa LCD memiliki latensi 500ms
Saat SSD NVMe pertama kali muncul, saya pernah bercanda, “sekarang rasanya seperti punya RAM 2TB”
Tapi sekarang server GPU benar-benar dilengkapi RAM 2TB — pencapaian engineering yang luar biasa
Sekarang saya menyesal tidak membelinya saat itu
Berdasarkan pengalaman mengoptimalkan database OLAP di lingkungan dengan konkurensi tinggi, bottleneck-nya sebagian besar adalah kecepatan memori
Bottleneck I/O awalnya bukan terkait pembacaan sekuensial, melainkan waktu seek
Saya paham inti artikelnya, tetapi poin ini tetap ingin saya tekankan
Kecepatan baca sekuensial tidak bisa diperbaiki lewat kode, jadi inti utamanya adalah optimasi akses non-sekuensial