عند اختيار تنسيق تسلسل الرسائل التي سيتم كتابتها إلى قائمة الانتظار أو السجل أو أي مكان آخر ، غالبًا ما يطرح عدد من الأسئلة التي تؤثر بطريقة ما على الاختيار النهائي. إحدى هذه المشكلات الأساسية هي سرعة التسلسل وحجم الرسالة المستلمة. نظرًا لوجود العديد من التنسيقات لهذه الأغراض ، فقد قررت اختبار بعضها ومشاركة النتائج.
إعداد الاختبار
سيتم اختبار التنسيقات التالية:
- تسلسل جافا
- سلمان
- أفرو
- Protobuf
- التوفير (ثنائي ، مدمج)
- Msgpack
تم اختيار Scala باعتباره PL.
ستكون أداة الاختبار الرئيسية هي
Scalameter .
سيتم قياس ومقارنة المعلمات التالية: الوقت الذي يستغرقه التسلسل وإلغاء التسلسل ، وحجم الملفات الناتجة.
سهولة الاستخدام ، وإمكانية تطور الدائرة وغيرها من المعالم الهامة في هذه المقارنة لن تشارك.
توليد المدخلات
من أجل نقاء التجارب ، يجب أولاً إنشاء مجموعة بيانات. تنسيق الإدخال هو ملف CSV. يتم إنشاء البيانات باستخدام `Random.next [...]` بسيطة للقيم الرقمية و `UUID.randomUUID ()` للسلسلة. تتم كتابة البيانات التي تم إنشاؤها إلى ملف CSV باستخدام
kantan . في المجموع ، تم إنشاء 3 مجموعات من سجلات 100K لكل منها:
- بيانات مختلطة - 28 ميغابايت
بيانات مختلطة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
- خطوط فقط - 71 ميغابايت
OnlyStrings 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
- الأرقام فقط (طويلة) - 20 ميغابايت
OnlyLongs 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
يتكون كل إدخال من 20 حقلاً. قيمة كل حقل اختياري.
تجريب
خصائص جهاز الكمبيوتر الذي تم إجراء الاختبار عليه ، وإصدار سكالا وجافا:
الكمبيوتر الشخصي: 1.8 غيغاهرتز Intel Core i5-5350U (2 النوى المادية) ، 8 جيجابايت 1600 ميجاهرتز DDR3 ، SSD SM0128G
إصدار جافا: 1.8.0_144-b01 ؛ نقطة فعالة: بناء 25.144-b01
نسخة سكالا: 2.12.8
تسلسل جافا
سلمان
أفرو
تم إنشاء دائرة Avro أثناء التنقل قبل الاختبار المباشر. لهذا ، تم
استخدام مكتبة
avro4s .
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; }
لإنشاء فئات protobuf3 ، تم
استخدام البرنامج المساعد
ScalaPB .
تقتير
مخطط التوفير 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, }
لإنشاء فصول تشبه scala ، تم استخدام البرنامج المساعد
Scrooge .
Msgpack
المقارنة النهائية



دقة النتائجمهم: نتائج التسلسل وسرعة إلغاء التسلسل ليست دقيقة بنسبة 100 ٪. يوجد خطأ كبير. على الرغم من حقيقة أن الاختبارات قد أجريت عدة مرات مع ارتفاع درجة حرارة JVM ، إلا أنه من الصعب وصف النتائج مستقرة ودقيقة. لهذا السبب أوصي بشدة بعدم إجراء استنتاجات نهائية بشأن تنسيق تسلسل معين ، مع التركيز على الجداول الزمنية.
نظرًا لحقيقة أن النتائج ليست دقيقة تمامًا ، فلا يزال من الممكن إجراء بعض الملاحظات على أساسها:
- مرة أخرى ، تأكدنا من أن تسلسل جافا بطيء وليس الأكثر اقتصادا من حيث الإخراج. أحد الأسباب الرئيسية للعمل البطيء هو الوصول إلى حقول الكائنات باستخدام الانعكاس. بالمناسبة ، يتم الوصول إلى الحقول وتسجيلها بشكل أكبر ليس بالترتيب الذي أعلنته به في الفصل ، ولكن بالترتيب المعجمي. هذه مجرد حقيقة مثيرة للاهتمام ؛
- Json هو تنسيق النص الوحيد المقدم في هذه المقارنة. لماذا تأخذ البيانات المتسلسلة في json مساحة كبيرة - كل سجل مكتوب مع الدائرة. يؤثر هذا أيضًا على سرعة الكتابة إلى الملف: كلما زاد عدد وحدات البايت التي تحتاج إلى كتابتها ، زاد الوقت الذي تستغرقه. أيضًا ، لا تنس أن يتم إنشاء كائن json لكل سجل ، مما لا يقلل من الوقت أيضًا ؛
- عند إجراء تسلسل لكائن ما ، يقوم Avro بتحليل الدائرة من أجل تحديد كيفية معالجة حقل معين. هذه تكاليف إضافية تؤدي إلى زيادة في إجمالي وقت التسلسل ؛
- يتطلب Thrift ، بالمقارنة مع ، على سبيل المثال ، protobuf و msgpack ، مقدارًا أكبر من الذاكرة لكتابة حقل واحد ، حيث يتم حفظ معلومات التعريف الخاصة به إلى جانب قيمة الحقل. أيضًا ، إذا نظرت إلى ملفات الإخراج من التوفير ، يمكنك أن ترى أنه لا يشغل جزء صغير من إجمالي الحجم معرفات مختلفة لبداية ونهاية السجل وحجم السجل بأكمله كفاصل. كل هذا بالتأكيد يزيد فقط من الوقت الذي يقضيه في التعبئة والتغليف.
- يقوم Protobuf ، مثل التوفير ، بحزم معلومات التعريف ، ولكنه يجعله أفضل قليلاً. كما أن الاختلاف في خوارزمية التعبئة والتفريغ يسمح لهذا التنسيق في بعض الحالات بالعمل بشكل أسرع من الحالات الأخرى ؛
- Msgpack يعمل بسرعة كبيرة. أحد أسباب السرعة هو حقيقة أنه لا يتم إجراء تسلسل لمعلومات التعريف الإضافية. هذا أمر جيد وسيئ: جيد لأنه يشغل مساحة صغيرة على القرص ولا يتطلب وقتًا إضافيًا للتسجيل ، وهو سيئ لأنه بشكل عام لا يوجد شيء معروف عن بنية التسجيل ، لذا حدد كيفية حزم هذا الملف أو ذاك وتفريغه يتم تنفيذ قيمة مختلفة لكل حقل من كل سجل.
بالنسبة إلى أحجام ملفات الإخراج ، فإن الملاحظات واضحة تمامًا:
- تم الحصول على أصغر ملف للاتصال الرقمي من msgpack؛
- تبين أن الملف الأصغر لمجموعة السلسلة موجود في الملف المصدر :) باستثناء الملف المصدر ، فاز avro بهامش صغير من msgpack و protobuf؛
- أصغر ملف للمجموعة المختلطة جاء مرة أخرى من msgpack. ومع ذلك ، فإن الفجوة ليست ملحوظة للغاية وأفرو و protobuf قريبة جدا ؛
- أكبر الملفات جاءت من json. ومع ذلك ، يجب تقديم ملاحظة مهمة - تنسيق النص json ومقارنتها بالثنائي في الحجم (ومن حيث سرعة التسلسل) ليست صحيحة تمامًا ؛
- تم الحصول على أكبر ملف للاتصال الرقمي من تسلسل java القياسي ؛
- كان أكبر ملف لمجموعة سلسلة التوفير ثنائي؛
- وكان أكبر ملف لمجموعة مختلطة التوفير ثنائي. بعد ذلك يأتي تسلسل جافا القياسية.
تحليل الشكل
الآن ، دعونا نحاول فهم النتائج من خلال مثال إجراء تسلسل لسلسلة مكونة من 36 حرفًا (UUID) دون مراعاة فواصل البيانات بين السجلات ومعرفات مختلفة لبداية ونهاية السجل - سجل حقل سلسلة واحد فقط ، ولكن مع الأخذ في الاعتبار معلمات مثل ، على سبيل المثال ، نوع الحقل ورقمه . يغطي دراسة تسلسل السلسلة بالكامل جوانب متعددة في وقت واحد:
- تسلسل الأرقام (في هذه الحالة ، طول السلسلة)
- سلسلة التسلسل
لنبدأ مع avro. نظرًا لأن جميع الحقول من النوع "Option" ، فسيكون مخطط هذه الحقول كما يلي: "union: [" null "،" string "]". مع العلم بذلك ، يمكنك الحصول على النتيجة التالية:
بايت واحد للإشارة إلى نوع السجل (فارغ أو سلسلة) ، بايت واحد لكل طول الخط (بايت واحد لأن أفرو يستخدم
متغير الطول لكتابة أعداد صحيحة) و 36 بايت لكل سطر نفسه. المجموع: 38 بايت.
الآن النظر في msgpack. يستخدم Msgpack أسلوبًا مشابهًا
لطول متغير لكتابة أعداد صحيحة:
spec . دعنا نحاول حساب مقدار ما يتطلبه الأمر فعلاً لكتابة حقل سلسلة: 2 بايت لكل طول الخط (نظرًا لأن السلسلة> 31 بايت ، ثم ستحتاج إلى 2 بايت) ، 36 بايت لكل البيانات. المجموع: 38 بايت.
يستخدم Protobuf أيضًا
متغير الطول لتشفير الأرقام. ومع ذلك ، بالإضافة إلى طول السلسلة ، يضيف protobuf بايت آخر مع رقم ونوع الحقل. المجموع: 38 بايت.
لا يستخدم التوفير
الثنائي أي تحسين لكتابة طول السلسلة ، ولكن بدلاً من 1 بايت لكل رقم ونوع الحقل ، يأخذ التوفير 3. وبالتالي ، يتم الحصول على النتيجة التالية: 1 بايت لكل رقم حقل ، 2 بايت لكل نوع ، 4 بايت لكل طول الخط ، 36 بايت لكل الخط. المجموع: 43 بايت.
يستخدم Thrift
المضغوط ، على عكس الثنائي ، الأسلوب
المتغير الطول لكتابة أعداد صحيحة ، وإذا كان ذلك ممكنًا ، يستخدم سجل رأس حقل مختصر. بناءً على ذلك ، نحصل على: 1 بايت لنوع ورقم الحقل ، 1 بايت للطول ، 36 بايت للبيانات. المجموع: 38 بايت.
استغرق تسلسل جافا 45 بايت لكتابة سلسلة ، منها 36 بايت - سلسلة ، 9 بايت - 2 بايت في الطول و 7 بايت لبعض المعلومات الإضافية ، والتي لم أستطع فك تشفيرها.
تبقى فقط avro و msgpack و protobuf و التوفير المضغوط. سيتطلب كل من هذه التنسيقات 38 بايت لكتابة utf-8 أسطر 36 حرفًا. لماذا ، إذن ، عند تعبئة سجلات السلسلة 100k لم تحصل أفرو على كمية أقل ، على الرغم من أن نظامًا غير مضغوط قد تم كتابته مع البيانات؟ يحتوي Avro على هامش صغير من التنسيقات الأخرى والسبب في هذه الفجوة هو عدم وجود 4 بايت إضافية لكل طول التعبئة للسجل بأكمله. والحقيقة هي أنه لا msgpack ، ولا protobuf ، ولا الادخار لديها فاصل سجل خاص. لذلك ، لكي أفرغ السجلات بشكل صحيح ، احتجت إلى معرفة الحجم الدقيق لكل سجل. إن لم يكن لهذه الحقيقة ، إذن ، مع وجود احتمال كبير ، فإن msgpack سيكون له ملف أصغر.
بالنسبة لمجموعة البيانات العددية ، كان السبب الرئيسي لفوز msgpack هو عدم وجود معلومات المخطط في البيانات المحزومة وأن البيانات كانت قليلة. سوف يستغرق التوفير و protobuf أكثر من 1 بايت إلى القيم الفارغة بسبب الحاجة إلى حزم معلومات حول نوع ورقم الحقل. تتطلب Avro و msgpack 1 بايت بالضبط لكتابة قيمة فارغة ، ولكن avro ، كما ذكرنا سابقًا ، يحفظ الدائرة بالبيانات.
Msgpack معبأة أيضا في ملف أصغر ومجموعة مختلطة ، والتي كانت أيضا متفرق. أسباب هذا كلها نفس العوامل.
وبالتالي ، اتضح أن البيانات المعبأة في msgpack تشغل أقل مساحة. هذا عبارة عادلة - لم يكن من أجل لا شيء تم اختيار msgpack كتنسيق تخزين البيانات لـ tarantool و aerospike.
استنتاج
بعد الاختبار ، يمكنني استخلاص النتائج التالية:
- من الصعب الحصول على نتائج قياسية مستقرة ؛
- اختيار تنسيق هو المفاضلة بين سرعة التسلسل وحجم الإخراج. في الوقت نفسه ، لا تنسَ معلمات مهمة مثل راحة استخدام التنسيق وإمكانية تطور المخطط (غالبًا ما تلعب هذه المعلمات دورًا رئيسيًا).
يمكن العثور على الكود المصدري هنا:
github