Cara Membuat Dekoder JSON Tercepat Di Go
Bagaimana saya membuat dekoder JSON tercepat di Go
Motivasi
Saat saya mulai meningkatkan dalam bahasa Go, saya terkejut menyaksikan seberapa banyak decoder JSON pihak ketiga yang sudah dibuat (lepas dari style komunitas bahasa Go yang lakukan semua dengan library standar dibanding dengan JavaScript, dan lain-lain.).
Tetapi, sesudah mencoba, baik besar atau kecil, saya mengetahui jika masing-masing banyak memiliki permasalahan yang belum pasti mereka bagi. Saya akan menjelaskannya lebih detil pada bagian berikut ini.
Permasalahan dengan decoder JSON selama ini
Saya tidak mengatakan library tertentu.
- Tergantung pada CPU (misalkan, menggunakan perakitan dan bukannya memperoleh peluang untuk jadi lebih cepat, non-AMD64 tidak lagi didukung. Misalkan, M1 dan Raspberry Pi ialah arm64)
- Tidak mudah digunakan (tidak cocok dengan paket standard, sulit atau mungkin tidak intuitif untuk dipakai, dan lain-lain.)
- Dibutuhkan penyiapan (pengguna perlu menulis switch-case atau menggunakan pembuatan code, yang menambah langkah tambahan dan memperumit jalur kerja peningkatan).
- Tidak terawat secara baik (versi bahasa baru tidak didukung, tergantung pada paket EOL, dan lain-lain.)
- Apalagi tidak cepat! (Banyak menggunakan refleksi, support kurang kuat untuk io.Reader, dan lain-lain.)
1 | Ketergantungan CPU → mereproduksi dengan Pure Go. Bahkan jika itu lebih lambat daripada sesuatu yang menggunakan JIT atau SIMD dalam perakitan, saya pribadi berpikir itu sangat berharga. |
2 | Tidak mudah digunakan / Diperlukan persiapan → Paket bahasa Go standar dirancang agar intuitif dan mudah digunakan, jadi hanya kompatibilitas yang tercapai! |
3 | Lagipula tidak cepat! → Sambil berpegang pada dua poin di atas, kami akan selalu berusaha untuk berkembang dengan kecepatan tinggi. |
Untuk mencapai hal ini, kami membuat sugawarayuuta/soneta, yang kami perkenalkan di sini.
sugawarayuuta/soneta
https://github.com/sugawarayuta/soneta
1 | Hampir kompatibel dengan decoder paket standar (akan sepenuhnya kompatibel di masa mendatang) |
2 | Cepat. Kira-kira 5 kali lebih cepat dari paket standar, lebih cepat dari json-iterator/go dan goccy/go-json, dan dalam beberapa kasus lebih cepat dari bytedance/sonic (yang mengklaim sebagai yang tercepat). |
Benchmark
https://github.com/RichardHightower/json-parsers-benchmark/blob/master/data/citm_catalog.json
BenchmarkEncodingJson-4 56 19289518 ns/op 5850983 B/op 33049 allocs/op
BenchmarkSonnet-4 313 3477650 ns/op 905760 B/op 5469 allocs/op
BenchmarkGoccyGoJson-4 247 4855301 ns/op 2989978 B/op 14323 allocs/op
BenchmarkJsonIteratorGo-4 225 5163313 ns/op 1339385 B/op 34599 allocs/op
BenchmarkSegmentioEncoding-4 130 9156341 ns/op 5129332 B/op 3303 allocs/op
BenchmarkBytedanceSonic-4 274 4235117 ns/op 3848331 B/op 10505 allocs/op
Di atas. The yardstick adalah yang menjadi standar dengan Go
Jika kecepatan bukan masalah untuk proyek Anda, tidak ada masalah, tetapi pengodean paket standar/json cukup lambat.
Jika Anda tertarik, kami sarankan Anda untuk mengukurnya sendiri.
Implementasi decoder
- Buat peta dan simpan pemrosesan khusus type.
- Tempat di mana peta dapat diganti harus berupa slices atau arrays dengan fixed length.
- Dalam beberapa kasus, buat peta Anda sendiri.
- Aritmatika pointer
- buka: nama tautan
- mapassign_faststr
Peta bahasa Go mempunyai fitur yang tombolnya bisa dipakai bila sebanding.
Pointer Go sebanding.
Anda dapat memperoleh type secara aktif dengan mentransmisikan nilai menggunakan tidak aman.
Type yang Anda dapatkan ialah pointer, disebutkan *rtype dalam paket `reflect`, apabila typenya sama, alamatnya akan sama
Ingat hal di atas, kita bisa membuat peta di mana *rtype ialah kunci dan peranan ialah nilai. Ini kurangi berapakah kali refleksi dipakai dari kedua kalinya.
Misalnya, jika Anda ingin menggunakan byte sebagai kunci, Anda dapat memanfaatkan fakta bahwa nilai maksimum hanya 256 dan membuat arrays dengan fixed length dan menggantinya seperti [256]bool
Dalam kasus saya, saya menggunakan metode hashing Robin Hood untuk membuat peta yang dapat diatur dengan string dan didapatkan dengan []byte menggunakan paket standar `hash/maphash`.
Anehnya, itu lebih cepat di lingkungan saya daripada saat saya menggunakan xxhash, dll.
Di Go 1.17, `unsafe.Add` ditambahkan untuk mempermudah melakukan operasi pointer.
Saya sering menggunakannya dalam paket saya, misalnya, ketika saya ingin mengakses bidang dalam suatu struktur, saya dapat menggunakan structType untuk mendapatkan offsetnya.
Hal yang sama berlaku untuk array, dan dengan unsafe.Add(pointer, array length*element type size) saya bisa mendapatkan pointer baru untuk menambahkan item baru ke array
Anda dapat menggunakan ini dengan membuat alias, misalnya jika Anda memiliki fitur yang tidak diekspor dalam paket refleksi atau runtime. Meskipun saat ini belum selesai, Anda dapat memeriksa apakah ini berfungsi dengan fungsi init(), dll., dan jika tidak, kembali ke library standar, dll., untuk menghilangkan rasa takut mengakses fitur yang tidak diekspor.
Saat membuat alias mapassign_faststr dari refleksi dengan cara di atas, melihat implementasi lapisan bawah (saya perhatikan dengan mengikuti jejak tumpukan saat panik) bahwa setelah mengalokasikan lokasi, salinan dibuat dari nilai dan dimasukkan saya menemukan bahwa itu adalah menyisipkan dengan membuat salinan dari nilai setelah mengalokasikan lokasi. Sebagai gantinya, saya membuat alias dari runtime, mengalokasikan, dan kemudian menetapkan langsung ke pointer itu, yang sangat mengurangi alokasi.
Artikel Terkait Lainnya :
- Mengapa Anda Membutuhkan Aplikasi Web Progresif Untuk Ecommerce?
- Beberapa Niche Alternatif Buat Kamu Yang Lulusan IT Tapi Gak Bisa Ngoding
- Ini Dia Cara Melatih Logika Programming Kamu Agar Terasah
- Supaya Gak Terlihat Cupu! Ini Dia Aplikasi Alternatif Untuk Mengelola Database MySQL Yang Digunakan Oleh Senior Programmer
- Beberapa Niche Alternatif Buat Kamu Yang Lulusan IT Tapi Gak Bisa Ngoding