Cara Membuat Dekoder JSON Tercepat Di Go - CRUDPRO

Cara Membuat Dekoder JSON Tercepat Di Go

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.)
No
Tujuan decoder JSON di masa mendatang
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

No
Fitur
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.
  • 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.

  • Tempat di mana peta dapat diganti harus berupa slices atau arrays dengan fixed length.
  • 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 beberapa kasus, buat peta Anda sendiri.
  • 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.

  • Aritmatika pointer
  • 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

  • buka: nama tautan
  • 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.

  • mapassign_faststr
  • 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.