ستقارن هذه المقالة أداء العديد من خوارزميات الضغط شائعة الاستخدام. أظهرت النتائج أن بعض الخوارزميات لا تزال تعمل بشكل صحيح في ظل قيود وحدة المعالجة المركزية للغاية.
تشمل المقارنات في المقالة:
JDK GZIP - هذه خوارزمية بطيئة مع نسبة ضغط عالية ، والبيانات المضغوطة مناسبة للاستخدام على المدى الطويل. java.util.zip.gzipinputstream / gzipoutputstream في JDK هو تنفيذ هذه الخوارزمية.
JDK Deflate - هذه خوارزمية أخرى في JDK (يتم استخدام هذه الخوارزمية في ملفات ZIP). ما يجعل الأمر مختلفًا عن GZIP هو أنه يمكنك تحديد مستوى ضغط الخوارزمية بحيث يمكنك موازنة وقت الضغط وحجم ملف الإخراج. المستويات الاختيارية هي 0 (غير مضغوطة) ، و 1 (مضغوط سريع) إلى 9 (مضغوط بطيء). تنفيذها هو java.util.zip.deflateroutputstream/inflaterinputStream.
تنفيذ Java لخوارزمية ضغط LZ4 - هذه هي أسرع سرعة ضغط بين الخوارزميات المقدمة في هذه المقالة. بالمقارنة مع أسرع انحراف ، فإن نتائج ضغطها أسوأ قليلاً.
Snappy - هذه خوارزمية ضغط شائعة جدًا تم تطويرها بواسطة Google. ويهدف إلى توفير خوارزميات الضغط بسرعة جيدة نسبيا ونسبة الضغط.
اختبار الضغط
استغرق الأمر مني أيضًا الكثير من الوقت لمعرفة الملفات المناسبة لاختبار ضغط البيانات والوجود على أجهزة كمبيوتر Java لمطوري Java (لا أريدك أن يكون لديك بضع مئات من ميغابايت من الملفات لتشغيل هذا الاختبار). أخيرًا ، اعتقدت أن معظم الناس يجب أن يكون لديهم وثائق مثبتة محليًا لـ JDK. لذلك ، قررت دمج دليل Javadoc بأكمله في ملف واحد - ربط جميع الملفات. يمكن القيام بذلك بسهولة باستخدام أمر TAR ، ولكن ليس الجميع مستخدم Linux ، لذلك كتبت برنامجًا لإنشاء هذا الملف:
الفئة العامة inputGenerator {private static final string javadoc_path = "your_path_to_jdk/docs" ؛ الملف النهائي الثابت العام file_path = ملف جديد ("your_output_file_path") ؛ ثابت {try {if (! file_path.exists ()) makejavadocfile () ؛ } catch (ioException e) {E.PrintStackTrace () ؛ }} private static void makejavadocfile () يلقي iOexception {try (OutputStream OS = جديد bufferedoutputStream (fileOutputStream جديد (file_path) ، 65536)) {appenddir (OS ، ملف جديد (Javadoc_Path)) ؛ } system.out.println ("ملف javadoc الذي تم إنشاؤه") ؛ } private static void AppendDir (Final OutputStream OS ، جذر الملف النهائي) يلقي ioException {for (file f: root.listfiles ()) {if (f.isdirectory ()) appenddir (os ، f) ؛ else files.copy (f.topath () ، OS) ؛ }}}حجم الملف بأكمله على الجهاز الخاص بي هو 354،509،602 بايت (338 ميجابايت).
امتحان
في البداية أردت قراءة الملف بأكمله في الذاكرة ثم ضغطه. ومع ذلك ، تظهر النتائج أنه حتى آلات 4G يمكن أن تنفد بسهولة من مساحة ذاكرة الكومة.
لذلك قررت استخدام ذاكرة التخزين المؤقت للملف لنظام التشغيل. إطار الاختبار الذي نستخدمه هنا هو JMH. سيتم تحميل هذا الملف في ذاكرة التخزين المؤقت بواسطة نظام التشغيل خلال مرحلة الاحماء (سيتم ضغطه مرتين في مرحلة الاحماء). سأضغط المحتوى في دفق BytearRayoutputStream (أعرف أن هذه ليست أسرع طريقة ، لكنها مستقرة نسبيًا لكل اختبار ولا يستغرق وقتًا في كتابة البيانات المضغوطة إلى القرص) ، لذلك هناك حاجة إلى بعض مساحة الذاكرة لتخزين هذا الإخراج.
فيما يلي الفئة الأساسية لفئة الاختبار. تختلف جميع الاختبارات فقط في التطبيقات المختلفة لتيار الإخراج المضغوط ، بحيث يمكنك إعادة استخدام فئة قاعدة الاختبار هذه وإنشاء دفق من تطبيق StreamFactory:
outputtimeUnit (timeUnit.millisEconds) state (Scope.Thread) fork (1) warmup (تكرار = 2) measurement (التكرار = 3) benchmarkmode (mode.singleshottime) الفئة العامة {المسار المحمي m_inputfile ؛ setup public void setup () {m_inputfile = inputgenerator.file_path.topath () ؛ } الواجهة StreamFactory {public outputstream getStream (Final OutderStream stream) يلقي ioException ؛ } public int basebenchmark (Final Streamfactory Factory) يلقي ioException {try (bytearrayoutputstream bos = new bytearrayoutputstream ((int) m_inputfile.tofile () os.flush () ؛ إرجاع bos.size () ؛ }}}تتشابه حالات الاختبار هذه (تتوفر رمز المصدر الخاص بها في نهاية المقالة) ، ويتم سرد مثال واحد فقط هنا - فئة اختبار JDK Deflate ؛
الطبقة العامة jdkdeflateTest يمتد testparent {param ({"1" ، "2" ، "3" ، "4" ، "5" ، "6" ، "7" ، "8" ، "9"}) public int m_lvl ؛ @benchmark public int diflate () يلقي ioException {return basebenchmark (new StreamFactory () {Override public OutputStream getTream (outputStream ansherlytream) يلقي ioException {final deflater deflater = new deflater (m_lvl ، true) ؛ }}نتائج الاختبار
حجم ملف الإخراج
أولاً ، دعونا نلقي نظرة على حجم ملف الإخراج:
|| التنفيذ || حجم الملف (بايت) |||| gzip || 64،200،201 |||| snappy (Normal) || 138،250،196 |||| snappy (framed) || 101،470،113 ||||| LZ4 (Fast) || 98،316،501 |||| LZ4 (High) || 82،076،909 ||| diflate (lvl = 1) || 78،369،711 |||| deflate (lvl = 2) || 75،261،711 | (LVL = 4) || 68،090،059 || |؛ || 63،839،200 |||
يمكن ملاحظة أن حجم الملف يختلف اختلافًا كبيرًا (من 60 ميجابايت إلى 131 ميجابايت). دعونا نلقي نظرة على المدة التي يستغرقها طرق ضغط مختلفة.
وقت الضغط
|| التنفيذ || الضغط Time (ms) |||| snappy.framedoutput || 2264.700 |||| snappy.normaloutput || 2201.120 |||| lz4.testfastnative || 1056.326 |||| outestfastunsa fe || 1346.835 |||| lz4.testfastsafe || 1917.929 |||||| lz4.testhighnative || 7489.958 |||| lz4 || 14413.622 |؛ || 7896.572 |؛ || 10351.887 ||
ثم ندمج وقت الضغط وحجم الملف في جدول لحساب إنتاجية الخوارزمية ومعرفة الاستنتاجات التي يمكن استخلاصها.
الإنتاجية والكفاءة
|| التنفيذ || الوقت (ms) || حجم الملف غير المضغوط || الإنتاجية (mb/sec) ||| حجم الملف المضغوط (mb) |||| snappy.normaloutput || 2201.12 || 338 || 153.558188586 || 131.8454747432 || 149.2471409017 || 96.7693328857 |||| LZ4.TestFastNister || 1056.326 || 338 || 319.9769768045 || 93.7557220459 ||| سعد. || 176.2317583185 || 93.7557220459 ||||| LZ4.TestFastunsafe || 1346.835 || 338 || 250.9587291688 || 93.7557220459 ||||||||||||||؛ || 45.1270888301 || 78.2680511475 |||| lz4.testhighsafe || 14413.622 || 338 || 23.4500391366 || 78.2680511475 ||||| it lz4.testhighthight || 32.7933332124 || 78.2680511475 |||| diflate (lvl = 1) || 4522.644 || 338 || 74.7350443679 || 74.7394561768 |||| اطلاع (lvl = 2) || 71.5120374012 || 71.7735290527 |||| diflate (lvl = 3) || 5081.934 || 338 || 66.5101120951 || || 50.1524605124 || 64.9452209473 |||| diflate (lvl = 5) || 7896.572 || 338 || || 34.5472536415 || 61.2258911133 ||||| diflate (lvl = 7) || 10731.761 || 338 || 31.4952969974 || 61.04469932 | || 22.8991689295 || 60.8825683594 |؛ || 61.2258911133 ||
كما يمكن أن نرى ، فإن معظم التطبيقات غير فعالة للغاية: على معالجات Xeon E5-2650 ، يبلغ معدل الانكماش عالي المستوى حوالي 23 ميجابايت/ثانية ، وحتى GZIP هو 33 ميجابايت/ثانية فقط ، وهو أمر يصعب إرضائه. في الوقت نفسه ، يمكن أن تصل أسرع خوارزمية defalte إلى حوالي 75 ميجابايت/ثانية ، و Snappy هي 150 ميجابايت/ثانية ، ويمكن أن تصل LZ4 (Fast ، Jni Application) إلى 320 ميجابايت/ثانية!
يمكن رؤيته بوضوح من الجدول أن هناك حاليًا تطبيقان في وضع غير مؤات: Snappy أبطأ من LZ4 (ضغط سريع) ، والملف المضغوط أكبر. على العكس من ذلك ، يكون LZ4 (نسبة الضغط العالية) أبطأ من انحراف المستويات من 1 إلى 4 ، وحجم ملف الإخراج أكبر بكثير من حجم الانكماش من المستوى 1.
لذلك ، إذا كنت بحاجة إلى أداء "ضغط في الوقت الفعلي" ، فسوف أختار بالتأكيد في تطبيق JNI LZ4 (سريع) أو انكماش المستوى 1. بالطبع ، إذا كانت شركتك لا تسمح لمكتبات الطرف الثالث ، فيمكنك استخدام Deflate فقط. تحتاج أيضًا إلى التفكير بشكل شامل في عدد موارد وحدة المعالجة المركزية المجانية الموجودة ومكان تخزين البيانات المضغوطة. على سبيل المثال ، إذا كنت ترغب في تخزين البيانات المضغوطة إلى HDD ، فإن أداء 100 ميجابايت/ثانية لا يكون مفيدًا لك (على افتراض أن ملفك كبير بدرجة كافية) - ستصبح سرعة HDD عنق الزجاجة. إذا تم إخراج الملف نفسه إلى محرك الأقراص الثابتة SSD - حتى LZ4 سيظهر بطيئًا جدًا أمامه. إذا كنت ترغب في ضغط البيانات أولاً ثم إرسالها إلى الشبكة ، فمن الأفضل اختيار LZ4 ، لأن أداء ضغط DIPLATE75MB/S صغير جدًا مقارنةً بإنتاجية 125 ميجابايت/ثانية (بالطبع ، أعرف أن هناك أيضًا baotou في حركة المرور الشبكي ، ولكن حتى لو تم تضمينها ، فإن الفجوة كبيرة جدًا).
لخص
إذا كنت تعتقد أن ضغط البيانات بطيء للغاية ، فيمكنك النظر في تطبيق LZ4 (سريع) ، والذي يمكنه تحقيق سرعات ضغط النص بحوالي 320 ميجابايت/ثانية - لا ينبغي إدراك سرعة الضغط هذه لمعظم التطبيقات.
إذا كنت تقتصر على عدم القدرة على استخدام مكتبات الطرف الثالث أو ترغب فقط في الحصول على حل ضغط أفضل قليلاً ، فيمكنك التفكير في استخدام JDK Deflate (LVL = 1) للترميز وفك التشفير - يمكن للملف نفسه ضغط الملف نفسه إلى 75 ميجابايت/ثانية.
رمز المصدر
رمز مصدر اختبار ضغط جافا