บทความนี้จะเปรียบเทียบประสิทธิภาพของอัลกอริทึมการบีบอัดที่ใช้กันทั่วไปหลายประการ ผลการวิจัยพบว่าอัลกอริทึมบางอย่างยังคงทำงานได้อย่างถูกต้องภายใต้ข้อ จำกัด ของ CPU ที่ต้องการอย่างมาก
การเปรียบเทียบในบทความรวมถึง:
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 ส่วนใหญ่ (ฉันไม่ต้องการให้คุณมีไฟล์สองสามร้อยเมกะไบต์เพื่อเรียกใช้การทดสอบนี้) ในที่สุดฉันคิดว่าคนส่วนใหญ่ควรมีเอกสารสำหรับการติดตั้ง JDK ในท้องถิ่น ดังนั้นฉันจึงตัดสินใจรวมไดเรกทอรี Javadoc ทั้งหมดเข้ากับไฟล์เดียว - ประกบไฟล์ทั้งหมด สิ่งนี้สามารถทำได้อย่างง่ายดายด้วยคำสั่ง TAR แต่ไม่ใช่ทุกคนที่เป็นผู้ใช้ Linux ดังนั้นฉันจึงเขียนโปรแกรมเพื่อสร้างไฟล์นี้:
Public Class InputGenerator {สตริงสุดท้ายคงที่ javadoc_path = "your_path_to_jdk/docs"; ไฟล์สุดท้ายคงที่สาธารณะไฟล์ file_path = ไฟล์ใหม่ ("your_output_file_path"); คงที่ {ลอง {ถ้า (! file_path.exists ()) makejavadocfile (); } catch (ioexception e) {e.printstacktrace (); }} โมฆะคงที่ส่วนตัว makejavadocfile () พ่น IOException {ลอง (outputStream OS = ใหม่ bufferedOutputStream (ใหม่ fileOutputStream (file_path), 65536)) {appendDir (OS, ไฟล์ใหม่ (Javadoc_Path)); } system.out.println ("ไฟล์ javadoc ที่สร้างขึ้น"); } private static void appendDir (สุดท้าย OutputStream OS, Final File Root) พ่น IOException {สำหรับ (ไฟล์ f: root.listfiles ()) {ถ้า (f.isdirectory ()) ภาคผนวก (OS, F); else files.copy (f.topath (), OS); -ขนาดของไฟล์ทั้งหมดในเครื่องของฉันคือ 354,509,602 ไบต์ (338MB)
ทดสอบ
ตอนแรกฉันต้องการอ่านไฟล์ทั้งหมดลงในหน่วยความจำแล้วบีบอัดมัน อย่างไรก็ตามผลลัพธ์แสดงให้เห็นว่าแม้แต่เครื่อง 4G ก็สามารถหมดพื้นที่หน่วยความจำฮีปได้อย่างง่ายดาย
ดังนั้นฉันจึงตัดสินใจใช้แคชไฟล์ของระบบปฏิบัติการ กรอบทดสอบที่เราใช้ที่นี่คือ JMH ไฟล์นี้จะถูกโหลดลงในแคชโดยระบบปฏิบัติการในระหว่างขั้นตอนการอุ่นเครื่อง (จะถูกบีบอัดสองครั้งในขั้นตอนการอุ่นเครื่อง) ฉันจะบีบอัดเนื้อหาลงในสตรีม ByteArrayOutputStream (ฉันรู้ว่านี่ไม่ใช่วิธีที่เร็วที่สุด แต่มันค่อนข้างเสถียรสำหรับการทดสอบแต่ละครั้งและไม่ต้องใช้เวลาในการเขียนข้อมูลที่บีบอัดไปยังดิสก์) ดังนั้นจึงจำเป็นต้องมีพื้นที่หน่วยความจำบางอย่างในการเก็บเอาต์พุตนี้
ด้านล่างเป็นคลาสพื้นฐานของคลาสทดสอบ การทดสอบทั้งหมดนั้นแตกต่างกันเฉพาะในการใช้งานที่แตกต่างกันของสตรีมเอาต์พุตที่ถูกบีบอัดดังนั้นคุณสามารถนำคลาสฐานทดสอบนี้กลับมาใช้ใหม่และสร้างสตรีมจากการใช้งาน StreamFactory:
@OutputTimeUnit (timeUnit.milliseconds) @state (scope.thread) @fork (1) @warmup (iterations = 2) @measurement (ซ้ำ = 3) @benchmarkmode (mode.singleshottime) คลาสสาธารณะ @Setup การตั้งค่าโมฆะสาธารณะ () {m_inputFile = inputGenerator.file_path.topath (); } Interface StreamFactory {Public OutputStream GetStream (Final OutputStream anderlyingStream) พ่น IOException; } public int baseBenchmark (โรงงานกระแสสุดท้าย) พ่น IOException {ลอง (byteArrayOutputStream bos = ใหม่ byteArrayOutputStream ((int) m_inputfile.tofile (). length ()); OpputStream OS = Factory.GetStream (BOS) os.flush (); กลับ bos.size (); -กรณีทดสอบเหล่านี้คล้ายกันมาก (ซอร์สโค้ดของพวกเขามีอยู่ในตอนท้ายของบทความ) และมีเพียงตัวอย่างเดียวเท่านั้นที่แสดงอยู่ที่นี่ - คลาสทดสอบของ JDK deflate;
คลาสสาธารณะ JDKDefLateTest ขยาย testParent {@param ({"1", "2", "3", "4", "5", "6", "7", "8", "9"}) สาธารณะ int m_lvl; @Benchmark public int deflate () พ่น ioexception {return baseBenchmark (ใหม่ streamfactory () {@Override public outputstream getStream (outputstream onderlyingStream) โยน ioexception {deflater deflater = new -deflater (m_lvl, true); -ผลการทดสอบ
ขนาดของไฟล์เอาต์พุต
ก่อนอื่นมาดูขนาดของไฟล์เอาต์พุต:
|| การใช้งาน || ขนาดไฟล์ (ไบต์) |||| GZIP || 64,200,201 |||| snappy (ปกติ) || 138,250,196 ||| snappy (กรอบ) || 101,470,113 ||||| LZ4 (เร็ว) || 98,316,501 |||| LZ4 (สูง) || 82,076,909 ||| deflate (lvl = 1) || 78,369,711 |||| deflate (lvl = 2) || 75,261,711 (lvl = 4) || 68,090,059 || || deflate (lvl = 5) || 65,699,810 |||| deflate (lvl = 6) || 64,200,191 |||| | deflate (lvl = 7) || 64,013,638 |||| || 63,839,200 |||
จะเห็นได้ว่าขนาดไฟล์แตกต่างกันอย่างมาก (จาก 60MB ถึง 131MB) ลองมาดูกันว่าต้องใช้เวลานานแค่ไหนสำหรับวิธีการบีบอัดที่แตกต่างกัน
เวลาบีบอัด
|| การใช้งาน || การบีบอัด เวลา (MS) |||| snappy.framedOutput || 2264.700 |||| snappy.normaloutput || 2201.120 |||| lz4.testfastnative || 1056.326 ||| FE || 1346.835 |||| lz4.TestFastSafe || 1917.929 |||| lz4.TestHighnative || 7489.958 |||| lz4.Testhighunsafe || 10306.973 || 14413.622 |||| deflate (lvl = 1) || 4522.644 |||| deflate (lvl = 2) || 4726.477 ||| deflate (lvl = 3) || 5081.934 || 7896.572 |||| deflate (lvl = 6) || 9783.701 |||| deflate (lvl = 7) || 10731.761 ||| deflate (lvl = 8) || 14760.361 || 10351.887 ||
จากนั้นเรารวมเวลาการบีบอัดและขนาดไฟล์ไว้ในตารางเพื่อนับปริมาณงานของอัลกอริทึมและดูว่าสามารถสรุปได้ว่าข้อสรุปใด
ปริมาณงานและประสิทธิภาพ
|| การใช้งาน || เวลา (MS) || ขนาดไฟล์ที่ไม่บีบอัด || ปริมาณงาน (mb/sec) ||| ขนาดไฟล์บีบอัด (MB) ||| Snappy.normaloutput || 2201.12 || 338 || 153.5581885586 || 338 || 149.2471409017 || 96.7693328857 |||| lz4.Testfastnative || 1056.326 || 338 || 319.9769768045 || 93.757220459 | | || 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 || 32.7933332124 || 78.2680511475 |||| deflate (lvl = 1) || 4522.644 || 338 || 74.7350443679 || 74.7394561768 | | || 71.5120374012 || 71.7735290527 |||| deflate (lvl = 3) || 5081.934 || 338 || 66.5101120951 || 69.847106936 || 50.1524605124 || 64.9452209473 |||| deflate (lvl = 5) || 7896.572 || 338 || 42.8033835442 || 62.6564025879 | | || 34.5472536415 || 61.2258911133 ||||| deflate (lvl = 7) || 10731.761 || 338 || 31.4952969974 || 61.044692929292 || 22.8991689295 || 60.8825683594 |||| deflate (lvl = 9) || 14878.364 || 338 || 22.7175514727 || 60.873031662 || 32.651051929 || 61.2258911133 ||
ดังที่เห็นได้ว่าการใช้งานส่วนใหญ่ไม่มีประสิทธิภาพมาก: ในโปรเซสเซอร์ Xeon E5-2650, deflate ระดับสูงคือประมาณ 23MB/วินาทีและแม้แต่ GZIP ก็มีเพียง 33MB/วินาทีซึ่งอาจเป็นเรื่องยากที่จะตอบสนอง ในเวลาเดียวกันอัลกอริทึม defalte ที่เร็วที่สุดสามารถเข้าถึงได้ประมาณ 75MB/วินาที Snappy คือ 150MB/วินาทีและ LZ4 (เร็วการใช้งาน JNI) สามารถเข้าถึง 320MB/วินาทีที่เหลือเชื่อ!
มันสามารถเห็นได้อย่างชัดเจนจากตารางว่าปัจจุบันมีการใช้งานสองอย่างที่เสียเปรียบ: Snappy ช้ากว่า LZ4 (การบีบอัดอย่างรวดเร็ว) และไฟล์ที่บีบอัดมีขนาดใหญ่กว่า ในทางตรงกันข้าม LZ4 (อัตราส่วนการบีบอัดสูง) ช้ากว่าการลดระดับจากระดับ 1 ถึง 4 และขนาดของไฟล์เอาต์พุตมีขนาดใหญ่กว่าของ deflate จากระดับ 1
ดังนั้นหากคุณต้องการดำเนินการ "การบีบอัดแบบเรียลไทม์" ฉันจะเลือกอย่างแน่นอนในการใช้งาน LZ4 (เร็ว) JNI หรือระดับ 1 deflate แน่นอนหาก บริษัท ของคุณไม่อนุญาตให้มีห้องสมุดบุคคลที่สามคุณสามารถใช้งานได้เท่านั้น คุณต้องพิจารณาอย่างละเอียดว่ามีทรัพยากร CPU ฟรีจำนวนเท่าใดและมีการจัดเก็บข้อมูลที่ถูกบีบอัด ตัวอย่างเช่นหากคุณต้องการจัดเก็บข้อมูลที่ถูกบีบอัดไว้ที่ HDD ประสิทธิภาพที่สูงกว่า 100MB/s จะไม่เป็นประโยชน์สำหรับคุณ (สมมติว่าไฟล์ของคุณมีขนาดใหญ่พอ) - ความเร็วของ HDD จะกลายเป็นคอขวด หากไฟล์เดียวกันถูกส่งออกไปยังฮาร์ดไดรฟ์ SSD - แม้แต่ LZ4 ก็จะปรากฏช้าเกินไปต่อหน้า หากคุณต้องการบีบอัดข้อมูลก่อนจากนั้นส่งไปยังเครือข่ายมันเป็นการดีที่สุดที่จะเลือก LZ4 เนื่องจากประสิทธิภาพการบีบอัดของ deflate75MB/s นั้นเล็กมากเมื่อเทียบกับปริมาณงานที่ 125MB/s (แน่นอนฉันรู้ว่ามี baotou ในการรับส่งข้อมูลเครือข่าย
สรุป
หากคุณคิดว่าการบีบอัดข้อมูลช้ามากคุณสามารถพิจารณาการใช้งาน LZ4 (เร็ว) ซึ่งสามารถบรรลุความเร็วการบีบอัดข้อความประมาณ 320MB/วินาที - ความเร็วการบีบอัดดังกล่าวไม่ควรรับรู้สำหรับแอปพลิเคชันส่วนใหญ่
หากคุณถูก จำกัด ให้ไม่สามารถใช้ไลบรารีของบุคคลที่สามหรือต้องการมีโซลูชันการบีบอัดที่ดีขึ้นเล็กน้อยคุณสามารถพิจารณาใช้ JDK deflate (LVL = 1) สำหรับการเข้ารหัสและการถอดรหัส - ไฟล์เดียวกันสามารถบีบอัดไฟล์เดียวกันกับ 75MB/s
รหัสต้นฉบับ
ซอร์สโค้ดทดสอบการบีบอัด Java