Perbandingan Format Serialisasi

Ketika memilih format serialisasi pesan yang akan ditulis ke antrian, log, atau di tempat lain, sejumlah pertanyaan sering muncul yang entah bagaimana mempengaruhi pilihan akhir. Salah satu masalah utama ini adalah kecepatan serialisasi dan ukuran pesan yang diterima. Karena ada banyak format untuk tujuan tersebut, saya memutuskan untuk menguji beberapa dari mereka dan membagikan hasilnya.

Persiapan ujian


Format berikut akan diuji:

  1. Serialisasi Java
  2. Json
  3. Avro
  4. Protobuf
  5. Hemat (biner, ringkas)
  6. Msgpaket


Scala terpilih sebagai PL.
Alat pengujian utama adalah Scalameter .

Parameter berikut akan diukur dan dibandingkan: waktu yang dibutuhkan untuk membuat serial dan deserialize, dan ukuran file yang dihasilkan.

Kemudahan penggunaan, kemungkinan evolusi rangkaian dan parameter penting lainnya dalam perbandingan ini tidak akan berpartisipasi.

Pembuatan Input


Untuk kemurnian eksperimen, set data pertama-tama harus dihasilkan. Format input adalah file CSV. Data dihasilkan menggunakan `Random.next [...]` sederhana untuk nilai numerik dan `UUID.randomUUID ()` untuk nilai string. Data yang dihasilkan ditulis ke file csv menggunakan kantan . Secara total, 3 set data dari 100k catatan masing-masing dihasilkan:

  1. Data Campuran - 28 mb

    Data campuran
    final case class MixedData( f1: Option[String], f2: Option[Double], f3: Option[Long], f4: Option[Int], f5: Option[String], f6: Option[Double], f7: Option[Long], f8: Option[Int], f9: Option[Int], f10: Option[Long], f11: Option[Float], f12: Option[Double], f13: Option[String], f14: Option[String], f15: Option[Long], f16: Option[Int], f17: Option[Int], f18: Option[String], f19: Option[String], f20: Option[String], ) extends Data 

  2. Hanya garis - 71 mb

    Hanya tali
     final case class OnlyStrings( f1: Option[String], f2: Option[String], f3: Option[String], f4: Option[String], f5: Option[String], f6: Option[String], f7: Option[String], f8: Option[String], f9: Option[String], f10: Option[String], f11: Option[String], f12: Option[String], f13: Option[String], f14: Option[String], f15: Option[String], f16: Option[String], f17: Option[String], f18: Option[String], f19: Option[String], f20: Option[String], ) extends Data 

  3. Hanya angka (panjang) - 20 mb

    Hanya panjang
     final case class OnlyLongs( f1: Option[Long], f2: Option[Long], f3: Option[Long], f4: Option[Long], f5: Option[Long], f6: Option[Long], f7: Option[Long], f8: Option[Long], f9: Option[Long], f10: Option[Long], f11: Option[Long], f12: Option[Long], f13: Option[Long], f14: Option[Long], f15: Option[Long], f16: Option[Long], f17: Option[Long], f18: Option[Long], f19: Option[Long], f20: Option[Long], ) extends Data 


Setiap entri terdiri dari 20 bidang. Nilai setiap bidang adalah opsional.

Pengujian


Karakteristik PC tempat pengujian berlangsung, versi scala dan java:
PC: 1,8 GHz Intel Core i5-5350U (2 core fisik), 8 GB 1600 MHz DDR3, SSD SM0128G
Versi Java: 1.8.0_144-b01; Hotspot: build 25.144-b01
Versi scala: 2.12.8

Serialisasi Java


Data campuranHanya rinduHanya string
Serialisasi, ms3444.532586,235548.63
Deserialisasi, ms852.62617.652006.41
Ukuran, mb362486

Json


Data campuranHanya rinduHanya string
Serialisasi, ms5280.674358.135958.92
Deserialisasi, ms3347,202730.194039.24
Ukuran, mb5236124

Avro


Sirkuit Avro dibuat saat bepergian sebelum pengujian langsung. Untuk ini, perpustakaan avro4s digunakan .
Data campuranHanya rinduHanya string
Serialisasi, ms2146.721546.952829.31
Deserialisasi, ms692.56535.96944.27
Ukuran, mb221173

Protobuf


Skema protobuf
 syntax = "proto3"; package protoBenchmark; option java_package = "protobufBenchmark"; option java_outer_classname = "data"; message MixedData { string f1 = 1; double f2 = 2; sint64 f3 = 3; sint32 f4 = 4; string f5 = 5; double f6 = 6; sint64 f7 = 7; sint32 f8 = 8; sint32 f9 = 9; sint64 f10 = 10; double f11 = 11; double f12 = 12; string f13 = 13; string f14 = 14; sint64 f15 = 15; sint32 f16 = 16; sint32 f17 = 17; string f18 = 18; string f19 = 19; string f20 = 20; } message OnlyStrings { string f1 = 1; string f2 = 2; string f3 = 3; string f4 = 4; string f5 = 5; string f6 = 6; string f7 = 7; string f8 = 8; string f9 = 9; string f10 = 10; string f11 = 11; string f12 = 12; string f13 = 13; string f14 = 14; string f15 = 15; string f16 = 16; string f17 = 17; string f18 = 18; string f19 = 19; string f20 = 20; } message OnlyLongs { sint64 f1 = 1; sint64 f2 = 2; sint64 f3 = 3; sint64 f4 = 4; sint64 f5 = 5; sint64 f6 = 6; sint64 f7 = 7; sint64 f8 = 8; sint64 f9 = 9; sint64 f10 = 10; sint64 f11 = 11; sint64 f12 = 12; sint64 f13 = 13; sint64 f14 = 14; sint64 f15 = 15; sint64 f16 = 16; sint64 f17 = 17; sint64 f18 = 18; sint64 f19 = 19; sint64 f20 = 20; } 

Untuk menghasilkan kelas protobuf3, plugin ScalaPB digunakan .
Data campuranHanya rinduHanya string
Serialisasi, ms1169.40865.061856.20
Deserialisasi, ms113.5677.38256,02
Ukuran, mb221173

Barang bekas


Skema penghematan
 namespace java thriftBenchmark.java #@namespace scala thriftBenchmark.scala typedef i32 int typedef i64 long struct MixedData { 1:optional string f1, 2:optional double f2, 3:optional long f3, 4:optional int f4, 5:optional string f5, 6:optional double f6, 7:optional long f7, 8:optional int f8, 9:optional int f9, 10:optional long f10, 11:optional double f11, 12:optional double f12, 13:optional string f13, 14:optional string f14, 15:optional long f15, 16:optional int f16, 17:optional int f17, 18:optional string f18, 19:optional string f19, 20:optional string f20, } struct OnlyStrings { 1:optional string f1, 2:optional string f2, 3:optional string f3, 4:optional string f4, 5:optional string f5, 6:optional string f6, 7:optional string f7, 8:optional string f8, 9:optional string f9, 10:optional string f10, 11:optional string f11, 12:optional string f12, 13:optional string f13, 14:optional string f14, 15:optional string f15, 16:optional string f16, 17:optional string f17, 18:optional string f18, 19:optional string f19, 20:optional string f20, } struct OnlyLongs { 1:optional long f1, 2:optional long f2, 3:optional long f3, 4:optional long f4, 5:optional long f5, 6:optional long f6, 7:optional long f7, 8:optional long f8, 9:optional long f9, 10:optional long f10, 11:optional long f11, 12:optional long f12, 13:optional long f13, 14:optional long f14, 15:optional long f15, 16:optional long f16, 17:optional long f17, 18:optional long f18, 19:optional long f19, 20:optional long f20, } 

Untuk menghasilkan kelas hemat scala-seperti, plugin Scrooge digunakan.
BinerData campuranHanya rinduHanya string
Serialisasi, ms1274.69877,982168.27
Deserialisasi, ms220.58133.64514.96
Ukuran, mb371698

KompakData campuranHanya rinduHanya string
Serialisasi, ms1294.87900,022199.94
Deserialisasi, ms240.23232.53505.03
Ukuran, mb311498

Msgpaket


Data campuranHanya rinduHanya string
Serialisasi, ms1142.56791.551974.73
Deserialisasi, ms289.6080.36428.36
Ukuran, mb219.673

Perbandingan akhir


serialisasi

deserialisasi

ukuran

Akurasi hasil
Penting: hasil serialisasi dan kecepatan deserialisasi tidak 100% akurat. Ada kesalahan besar. Terlepas dari kenyataan bahwa tes dijalankan berkali-kali dengan tambahan pemanasan JVM, sulit untuk menyebut hasilnya stabil dan akurat. Itu sebabnya saya sangat merekomendasikan untuk tidak membuat kesimpulan akhir mengenai format serialisasi tertentu, dengan fokus pada garis waktu.


Mengingat fakta bahwa hasilnya tidak sepenuhnya akurat, beberapa pengamatan masih dapat dilakukan atas dasar mereka:

  1. Sekali lagi, kami memastikan bahwa serialisasi java lambat dan bukan yang paling ekonomis dalam hal output. Salah satu alasan utama untuk kerja lambat adalah mengakses bidang objek menggunakan refleksi. Omong-omong, bidang diakses dan selanjutnya dicatat bukan dalam urutan di mana Anda mendeklarasikannya di kelas, tetapi diurutkan dalam urutan leksikografis. Ini hanya fakta yang menarik;
  2. Json adalah satu-satunya format teks yang disajikan dalam perbandingan ini. Mengapa data serial di json membutuhkan banyak ruang jelas - setiap catatan ditulis bersama dengan sirkuit. Ini juga mempengaruhi kecepatan penulisan file: semakin banyak byte yang Anda butuhkan untuk menulis, semakin banyak waktu yang dibutuhkan. Juga, jangan lupa bahwa objek json dibuat untuk setiap catatan, yang juga tidak mengurangi waktu;
  3. Saat membuat serial objek, Avro menganalisis sirkuit untuk memutuskan lebih lanjut bagaimana memproses bidang tertentu. Ini adalah biaya tambahan yang menyebabkan peningkatan total waktu serialisasi;
  4. Hemat, dibandingkan dengan, misalnya, protobuf dan msgpack, membutuhkan jumlah memori yang lebih besar untuk menulis satu bidang, karena informasi metanya disimpan bersama dengan nilai bidang. Juga, jika Anda melihat file output penghematan, Anda dapat melihat bahwa tidak sedikit dari total volume ditempati oleh berbagai pengidentifikasi dari awal dan akhir catatan dan ukuran seluruh catatan sebagai pemisah. Semua ini tentu saja hanya menambah waktu yang dihabiskan untuk pengemasan;
  5. Protobuf, seperti penghematan, mengemas informasi meta, tetapi membuatnya sedikit lebih dioptimalkan. Juga, perbedaan dalam algoritma pengemasan dan pembongkaran memungkinkan format ini dalam beberapa kasus bekerja lebih cepat daripada yang lain;
  6. Msgpack bekerja sangat cepat. Salah satu alasan kecepatan adalah kenyataan bahwa tidak ada informasi meta tambahan yang diserialisasi. Ini baik dan buruk: baik karena membutuhkan sedikit ruang disk dan tidak memerlukan waktu perekaman tambahan, buruk karena secara umum tidak ada yang diketahui tentang struktur rekaman, jadi tentukan bagaimana mengemas dan membongkar ini atau itu nilai yang berbeda dilakukan untuk setiap bidang dari setiap catatan.

Adapun ukuran file output, pengamatannya cukup jelas:

  1. File terkecil untuk panggilan numerik diperoleh dari msgpack;
  2. File terkecil untuk set string ternyata berada di file sumber :) Kecuali untuk file sumber, avro dimenangkan oleh margin kecil dari msgpack dan protobuf;
  3. File terkecil untuk set campuran lagi berasal dari msgpack. Namun, kesenjangannya tidak begitu terlihat dan avro dan protobuf sangat dekat;
  4. File terbesar berasal dari json. Namun, komentar penting harus dibuat - format teks json dan membandingkannya dengan volume biner (dan dalam hal kecepatan serialisasi) tidak sepenuhnya benar;
  5. File terbesar untuk panggilan numerik diperoleh dari serialisasi java standar;
  6. File terbesar untuk rangkaian string adalah biner hemat;
  7. File terbesar untuk set campuran adalah biner hemat. Mengikuti itu datang serialisasi java standar.

Analisis Format


Sekarang mari kita coba untuk memahami hasil dengan contoh serialisasi string 36 karakter (UUID) tanpa memperhitungkan pemisah akun antara catatan, berbagai pengidentifikasi dari awal dan akhir catatan - hanya merekam 1 bidang string, tetapi dengan mempertimbangkan parameter akun seperti, misalnya, jenis bidang dan nomor . Pertimbangan serialisasi string sepenuhnya mencakup beberapa aspek sekaligus:

  1. Serialisasi nomor (dalam hal ini, panjang tali)
  2. Serialisasi string

Mari kita mulai dengan avro. Karena semua bidang bertipe `Opsi`, skema untuk bidang tersebut adalah sebagai berikut:` union: ["null", "string"] `. Mengetahui hal ini, Anda bisa mendapatkan hasil berikut:
1 byte untuk menunjukkan tipe record (null atau string), 1 byte per panjang garis (1 byte karena avro menggunakan panjang variabel untuk menulis bilangan bulat) dan 36 byte per baris. Total: 38 byte.

Sekarang pertimbangkan msgpack. Msgpack menggunakan pendekatan yang mirip dengan panjang variabel untuk menulis bilangan bulat: spec . Mari kita coba menghitung berapa sebenarnya yang dibutuhkan untuk menulis bidang string: 2 byte per panjang garis (karena string> 31 byte, diperlukan 2 byte), 36 byte per data. Total: 38 byte.

Protobuf juga menggunakan panjang variabel untuk menyandikan angka. Namun, selain panjang string, protobuf menambahkan byte lain dengan jumlah dan jenis bidang. Total: 38 byte.

Biner barang bekas tidak menggunakan optimasi apa pun untuk menulis panjang string, tetapi alih-alih 1 byte per nomor dan jenis bidang, barang bekas membutuhkan 3. Oleh karena itu, hasil berikut diperoleh: 1 byte per nomor bidang, 2 byte per jenis, 4 byte per panjang baris, 36 byte per baris tali. Total: 43 byte.

Kompak hemat, tidak seperti biner, menggunakan pendekatan panjang variabel untuk menulis bilangan bulat dan, jika mungkin, juga menggunakan catatan header bidang yang disingkat. Berdasarkan ini, kita mendapatkan: 1 byte untuk jenis dan jumlah bidang, 1 byte untuk panjang, 36 byte untuk data. Total: 38 byte.

Serialisasi Java membutuhkan 45 byte untuk menulis sebuah string, di antaranya 36 byte - sebuah string, panjangnya 9 byte - 2 byte dan 7 byte untuk beberapa informasi tambahan, yang tidak dapat saya dekripsi.

Hanya avro, msgpack, protobuf, dan penghematan barang yang tersisa. Masing-masing format ini akan membutuhkan 38 byte untuk menulis utf-8 baris sepanjang 36 karakter. Mengapa, kemudian, ketika mengemas catatan string 100k, avro mendapatkan jumlah yang lebih kecil, meskipun skema yang tidak terkompresi ditulis bersama dengan data? Avro memiliki margin kecil dari format lain dan alasan untuk kesenjangan ini adalah kurangnya tambahan 4 byte per panjang pengemasan dari keseluruhan rekaman. Faktanya adalah bahwa msgpack, atau protobuf, atau penghematan tidak memiliki pemisah catatan khusus. Oleh karena itu, agar saya dapat membongkar kembali catatan dengan benar, saya perlu mengetahui ukuran pasti dari setiap catatan. Jika bukan karena fakta ini, maka, dengan probabilitas tinggi, msgpack akan memiliki file yang lebih kecil.

Untuk satu set data numerik, alasan utama msgpack menang adalah kurangnya informasi skema dalam data yang dikemas dan bahwa data itu jarang. Hemat dan protobuf bahkan akan membutuhkan lebih dari 1 byte untuk mengosongkan nilai karena kebutuhan untuk mengemas informasi tentang jenis dan jumlah bidang. Avro dan msgpack membutuhkan tepat 1 byte untuk menulis nilai kosong, tetapi avro, sebagaimana telah disebutkan, menyimpan sirkuit dengan data.

Msgpack juga dikemas dalam file yang lebih kecil dan satu set campuran, yang juga jarang. Alasan untuk semua ini adalah faktor yang sama.

Dengan demikian, ternyata data yang dikemas dalam msgpack membutuhkan ruang paling sedikit. Ini adalah pernyataan yang adil - bukan karena tidak ada msgpack dipilih sebagai format penyimpanan data untuk tarantool dan aerospike.

Kesimpulan


Setelah pengujian, saya bisa menggambar kesimpulan berikut:

  1. Sulit untuk mendapatkan hasil benchmark yang stabil;
  2. Memilih format merupakan trade-off antara kecepatan serialisasi dan ukuran output. Pada saat yang sama, jangan lupa tentang parameter penting seperti kenyamanan menggunakan format dan kemungkinan evolusi skema (seringkali parameter ini memainkan peran dominan).

Kode sumber dapat ditemukan di sini: github

Source: https://habr.com/ru/post/id458026/


All Articles