Tantangan 1 Miliar Baris
(morling.dev)- One Billion Row Challenge (1BRC), yang berlangsung sepanjang Januari 2024, adalah tantangan performa untuk memproses file teks berisi 1 miliar baris dan menguji seberapa cepat Java bisa berjalan
- Input berupa teks sederhana dalam format
station;temperature, tetapi program harus menghitung suhu minimum, rata-rata, dan maksimum per stasiun pengamatan serta mencetaknya secara akurat berdasarkan urutan nama - Implementasi hanya boleh menggunakan Java; distribusi SDKMan dan build Early Access dari openjdk.net boleh digunakan, tetapi dependensi eksternal dilarang
- Peserta mengirimkan solusi melalui pull request ke repositori 1brc di GitHub, dan dapat membandingkan format jawaban serta performa dengan implementasi dasar yang disediakan
- Penilaian dilakukan di lingkungan Hetzner Cloud CCX33 yang sama, dijalankan 5 kali, lalu peringkat leaderboard ditentukan berdasarkan rata-rata 3 kali eksekusi setelah catatan tercepat dan terlambat dikeluarkan
Tugas Java untuk Mengagregasi 1 Miliar Baris Secepat Mungkin
- One Billion Row Challenge adalah tantangan performa Java yang berlangsung dari 1 Januari hingga 31 Januari 2024
- Peserta menulis program Java yang membaca pengukuran suhu dari file teks dan menghitung suhu minimum, rata-rata, dan maksimum untuk setiap stasiun cuaca
- Inti tingkat kesulitannya adalah ukuran file input yang mencapai 1.000.000.000 baris
- Input memiliki struktur sederhana, dengan satu nilai pengukuran di setiap baris
- Contoh:
Hamburg;12.0 - Contoh:
Bulawayo;8.9 - Contoh:
Palembang;38.8
- Contoh:
- Output harus mengurutkan nama stasiun pengamatan secara alfabetis dan menampilkan nilai
min/mean/maxuntuk masing-masing stasiun- Contoh:
{Abha=5.0/18.0/27.4, Abidjan=15.7/26.0/34.1, ...}
- Contoh:
Aturan Pengiriman dan Lingkungan Eksekusi
- Tujuannya adalah membuat implementasi Java tercepat yang melakukan tugas yang sama
- Optimisasi dapat memanfaatkan virtual thread, Vector API dan SIMD, optimisasi GC, kompilasi AOT, dan sebagainya
- Aturan dasarnya adalah sebagai berikut
- Kiriman harus ditulis dalam Java
- Distribusi Java yang tersedia di SDKMan dan build Early Access dari openjdk.net boleh digunakan
- Build EA dari proyek OpenJDK seperti Valhalla juga diizinkan
- Dependensi eksternal tidak boleh digunakan
- Peserta melakukan clone repositori 1brc dan mengirimkan implementasinya sesuai instruksi README
- Implementasi dasar disediakan sebagai acuan pembanding dan untuk memeriksa format jawaban yang benar
- Pengiriman dilakukan dengan membuka pull request ke repositori upstream
Metode Perhitungan Leaderboard dan Berbagi di Komunitas
- Evaluasi dilakukan pada instans Hetzner Cloud CCX33
- Spesifikasinya adalah 8 dedicated vCPU, 32 GB RAM
- Waktu eksekusi end-to-end diukur dengan program
time - Setiap kiriman dijalankan 5 kali berturut-turut
- Eksekusi paling lambat dan paling cepat dikeluarkan
- Rata-rata waktu eksekusi dari 3 kali yang tersisa menjadi hasil untuk kiriman tersebut
- Hasil ditambahkan ke leaderboard
- Diskusi tentang teknik optimisasi berlanjut di discussion pada repositori GitHub
- Tersedia juga Show & Tell untuk berbagi implementasi dalam bahasa selain Java, dan implementasi 1BRC dalam Rust, Go, C++, dan lainnya dibagikan di sana
2 komentar
Komentar Hacker News
Solusi yang saat ini tampak berkinerja terbaik [0] sepertinya tidak mempertimbangkan tabrakan hash, jadi jika dataset memiliki cukup banyak kota yang berbeda, hasilnya kemungkinan bisa salah
Saya penasaran apakah ada yang saya lewatkan
[0] https://github.com/gunnarmorling/1brc/blob/main/src/main/jav...
Untuk sementara entri-entri tersebut sudah dihapus dari papan peringkat, dan kedua penulis sedang memperbaiki submission mereka sehingga nantinya akan ditambahkan kembali
[0] https://twitter.com/mtopolnik/status/1742652716919251052
Saya rasa dengan pendekatan berikut seluruhnya bisa diproses dalam 0,3 detik
Karena suhu memiliki satu angka desimal, dalam kasus umum sekitar 400 nilai sudah cukup, dan nama tempat juga terbatas sekitar 400, jadi kita bisa membuat lookup table sekitar 160 ribu kombinasi suhu×nama tempat
Buat otomatis sebuah state machine yang memetakan 160 ribu kombinasi ini ke bucket unik dalam hash table, apa pun posisi rotasinya di register 4 byte, lalu pada register status 32-bit lakukan lookup tabel transisi status dan XOR 4 byte berikutnya di setiap siklus
Cukup telusuri seluruh data pada kecepatan memori sambil menaikkan counter per status, dan karena statusnya hanya 65K, counter akan masuk cache
Dengan AVX512, per core bisa menjalankan 512 state machine 32-bit semacam ini secara paralel, jadi komputasi tidak akan menjadi bottleneck
Suhu tinggi/rendah atau nama tempat tak dikenal yang tidak terpetakan ke bucket valid dialihkan ke kode lambat, dan penanganan nilai minimum/maksimum juga bisa ditangani lewat escape seperti ini sehingga hanya terjadi beberapa ribu kali
Menurut saya metode ini bisa berjalan pada kecepatan memori hanya dengan satu core AVX512, jadi tidak ada keuntungan membaginya ke banyak core
Yang diperlukan hanyalah hash table berisi 400 entri, tiga nilai floating point untuk minimum·rata-rata·maksimum yang sedang berjalan, dan satu integer counter untuk memperbarui rata-rata
Bahkan memakai 16 byte untuk nama, semuanya akan muat dalam 16KB
Waktu eksekusi akan didominasi I/O, lalu kemungkinan berikutnya parsing JSON
Sebagian besar chip server x86 modern bisa me-retire 2 SIMD load per clock, jadi dengan AVX2 pada 1GHz kira-kira bisa mencapai 32GB/s, sehingga AVX-512 tidak mutlak diperlukan untuk memaksimalkan bandwidth per core
Namun jika membaca dari DRAM, batasnya akan jauh lebih cepat tercapai, di server umumnya kemungkinan tertahan di sekitar 10–16GB/s
Selama sebagian besar data meluap ke RAM, throughput satu core akan turun tajam, dan pada pekerjaan streaming besar, paralelisme multicore hampir selalu menguntungkan
Ini mudah dibuktikan dengan mengalokasikan blok memori yang jauh lebih besar daripada cache L3, melakukan page fault terlebih dahulu, lalu menjalankan vector load yang di-unroll (AVX2/AVX-512) dalam loop ketat
Saya juga ragu bagaimana register statusnya akan ditafsirkan. Jika di-XOR dengan input 4 byte, untuk nama tempat yang tidak terduga nilainya pada dasarnya bisa menjadi sembarang dari sekitar 4,7 miliar kemungkinan nilai
Bahkan untuk nama tempat yang diharapkan, jika lebih panjang dari 4 byte, bukankah masing-masing membutuhkan beberapa status agar bisa dibedakan dari nama lain yang memiliki prefiks sama?
Aturannya menyebut bahwa meskipun generator data memakai kumpulan nama stasiun pengamatan tetap, solusi apa pun harus bekerja untuk nama stasiun pengamatan UTF-8 arbitrer
Daripada membuang eksekusi paling lambat dan paling cepat lalu memakai rata-rata dari tiga eksekusi sisanya, menurut saya lebih baik membuang dua yang lambat atau cukup mengakui nilai tercepat
Saya tidak melihat alasan kuat untuk membuang hasil eksekusi yang bagus
Namun jika ada sedikit saja sumber nondeterminisme di dalam program—dan ini lebih umum daripada yang dikira—waktu terbaik kemungkinan kurang representatif
Terkait hal ini, https://tratt.net/laurie/blog/2019/minimum_times_tend_to_mis... adalah tulisan yang bagus
Dari sudut pandang yang memperdebatkan aturan, pada eksekusi pertama rasanya ingin menjalankan daemon latar belakang, memuat seluruh file ke memori dan mem-pinning-nya, lalu menarik cache lebih dulu supaya eksekusi berikutnya pada dasarnya hanya melakukan pemindaian linear
Menghitung hasil lebih dulu pada eksekusi pertama juga tampaknya bisa dilakukan, tergantung seberapa jauh aturan ditafsirkan secara longgar; angka-angka juga bisa di-parse lebih dulu ke format yang lebih padat lalu pada eksekusi berikutnya langsung dibaca sebagai akumulasi jumlah
Ini sama sekali tidak sesuai dengan tujuan kompetisi, tetapi dari aturan yang terlihat tampaknya tidak dilarang
Jika tidak suka prakalkulasi, masih ada trik seperti mengurutkan input lebih dulu, mem-parse lebih dulu, atau kompresi·pengurutan·tata letak memori yang sudah terurut
Secara ekstrem, bisa saja mem-patch skrip
calculate_timeagar mengembalikan 0 detik dan mengembalikan 9999 untuk pesaingMulai dari meng-hardcode jawaban dalam satu baris tanpa membaca input sama sekali, sampai pemrosesan dengan asumsi tidak mengetahui isi file, ada kira-kira satu miliar tingkat area abu-abu prakalkulasi
Kompetisinya bisa berubah menjadi kompetisi menentukan mana prakalkulasi yang adil dan mana yang tidak
Karena itu kompetisi machine learning tidak memperlihatkan data final kepada peserta
Disebutkan bahwa komputasi harus terjadi saat aplikasi dijalankan, dan file pengukuran tidak boleh diproses pada waktu build lalu hasilnya dipanggang ke dalam binary
Sepertinya ini sekadar masalah yang terikat pada kecepatan disk. Saya ragu optimasi seperti SIMD atau multithreading akan berarti
Memang tergantung pada jumlah stasiun observasi yang berbeda dan cara lookup hash, tetapi saya skeptis apakah dampaknya bisa terukur dibandingkan I/O
Sistem yang dirancang dengan asumsi hardware modern memanfaatkan hal ini, dan redpanda.com tempat saya bekerja juga salah satu contohnya
Parsing adalah bagian besar dari waktu komputasi, dan teknik SIMD seperti SWAR untuk menemukan delimiter bisa membantu
Jika ingin melihat implementasi algoritma seperti ini yang rapi, Stringzilla bagus: https://github.com/ashvardanian/StringZilla
Mengenai fakta bahwa setelah eksekusi pertama file sepenuhnya di-cache di memori, saya sudah menjawabnya di sini: https://news.ycombinator.com/item?id=38864034
Server umumnya punya cukup lane PCIe untuk memasang 15 SSD seperti ini, sehingga bandwidth I/O server berada pada level yang mirip dengan bandwidth memori
Server yang lebih mahal punya lane yang lebih cepat dan lebih banyak, seperti PCIe 5.0
File ini berisi 1 miliar baris, sekitar 1GB jika dikompresi, dan setelah eksekusi pertama yang dibuang file akan berada di memori, jadi dalam skenario ini bandwidth I/O tidak penting
Repositori GitHub menyebutkan ukurannya 12GB tanpa kompresi, tetapi itu tetap menegaskan bahwa bandwidth I/O tidak penting
Intinya, disk jarang menjadi bottleneck
Misalnya di Linux dengan ext2, besar kemungkinan seluruh file di-cache setelah eksekusi pertama, tetapi belum tentu demikian di ZFS
Dengan begitu angka muncul dari digit rendah ke digit tinggi, lalu delimiter dan string muncul, dan proses dapat dilanjutkan sampai bertemu EOF atau newline
Menurut aturan, submission harus bekerja benar untuk semua input, tetapi tampaknya boleh, dan mungkin memang sebaiknya, di-tuning untuk input tertentu yang dihasilkan oleh
create_measurements.shMisalnya bisa dibayangkan submission yang memakai fungsi hash sempurna yang disesuaikan dengan himpunan stasiun observasi yang diberikan
Dengan begitu optimasi yang overfit bisa dicegah
Byte yang lebih besar dari 127 berarti karakter UTF-8 multibyte
Untuk iseng, saya membandingkan kecepatan awk vs Java
Ini skrip yang dengan
awk -F';'mengakumulasi jumlah, hitungan, nilai minimum, dan maksimum per stasiun observasi, lalu menghitung dan mencetak rata-rata di ENDCaranya adalah membuat file CSV sebagai tabel eksternal dengan
file_fdw, lalu menghitungMIN,AVG,MAXdenganGROUP BY station_nameDi
clickhouse local, membacafile('measurements.txt', 'CSV', 'station String, t Float32'), mengelompokkanmin,max,avgper stasiun observasi, dan menjalankannya denganmax_threads = 8Sebagian besar waktu dihabiskan untuk parsing file
sumbisa menjadi cukup besar, sebaiknya gunakan rata-rata streamingMisalnya dengan cara seperti
new_mean = ((n*old_mean)+temp)/(n+1)Tantangan yang menarik, tapi sayang hanya untuk Java. Saya menantikan saat orang-orang mulai membuat bytecode JVM langsung dengan tangan
[0] https://github.com/gunnarmorling/1brc/discussions
Menarik. Rasanya seperti afterparty Advent of Code
Kalau ingin perbandingan yang adil antarbahasa, make dan waktu build juga harus dimasukkan. Saya sudah beberapa tahun tidak memakai Java/Maven, dan melihat unduhan
./mvnw clean verifymasih berlanjut sampai menit kedua membuat saya kembali ingat alasannyaDan untuk alat build kompilasi inkremental, Gradle lebih cepat
Proporsi yang layak dari waktu yang dibutuhkan untuk belajar pemrograman juga harus ditambahkan
Dalam tantangan seperti ini, versi yang sangat naif kemungkinan besar akan menang, dan menurut saya itu bukan hanya tidak realistis, tetapi juga bertentangan dengan maksud tantangan
cleanItu sama saja membuang cache lalu mengatakan lambat
Disebutkan bahwa dependensi eksternal tidak boleh digunakan
Di mata kuliah C di Czech Technical University ada tugas yang sangat mirip
Semua kiriman mahasiswa terus dinilai di papan peringkat, dan banyak mahasiswa menghabiskan puluhan jam untuk optimasi demi mendapatkan poin tambahan untuk nilai yang lebih baik—pada dasarnya poin status
Peringkat pertama 6 detik ya.. luar biasa.