Parallel SharpZipLib Zip erstellen

  • C#
  • .NET (FX) 4.5–4.8

Es gibt 4 Antworten in diesem Thema. Der letzte Beitrag () ist von Bluespide.

    Parallel SharpZipLib Zip erstellen

    Hallo zusammen,

    ich bin gerade dabei ein Zip-Archiv mit der ICSharpCode.SharpZipLib zu erstellen. Das Archiv besteht aus ca. 160 Einzeldateien. Das dauert jedoch sehr lange. Da die SharpZipLib anscheint von Haus aus nur 1 Cpu-Kern benutzt wollte ich das ganze selber versuchen zu optimieren. Die Idee ist, dass ich mit der Parallel.ForEach die einzelnen Dateien erstmal in ein MemoryStream komprimiere und dann nur noch zusammen in ein Archiv auf Platte schreibe. Ich finde bei der SharpZipLib aber keinen weg das ganze Umzusetzen. Ich kann einen ZipEntry erstellen und mir dann den komprimierten Stream holen, aber wie füge ich das ganze dann zu einem Archiv zusammen? Hat da jemand eine Idee? Es muss dabei eine gültige Zip-Datei bleiben und ich bin eigentlich an die SharpZipLib gebunden.
    @Bluespide Das funktioniert nur dann, wenn der aufgerufene Code parallelisierbar, also threadsafe ist.
    Wenn das nicht der Fall ist, hast Du keine Chance.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Ich habe mir das jetzt gerade noch einmal etwas genauer angeschaut und nach diesem(Zip) Wikipedia Artikel hört sich das für mich erstmal so an, als wäre mein Vorhaben umsetzbar, denn:
    Each file is stored separately, allowing different files in the same archive to be compressed using different methods. Because the files in a ZIP archive are compressed individually it is possible to extract them, or add new ones, without applying compression or decompression to the entire archive.


    Letztendlich alle Daten in den FileStream zu kopieren geht ja sehr schnell. Das was lange dauert ist die Komprimierung an sich, aber wenn jede Datei einzeln für sich selber komprimiert ist kann man das doch auf mehrere Threads aufteilen und dann nur noch die fertig komprimierten Streams synchron in die Datei schreiben.? Also ich kann eine einzelne Datei/Zip-Eintrag nicht einfach multithreaded Komprimieren, dafür müsste ich ja den Algorithmus kennen/verstehen und umschreiben (unmöglich), aber ich habe ca. 160 Dateien und würde nur das komprimieren einer ganzen Datei pro Thread auslagern. Die Hoffnung stirbt zuletzt, ich versuch das mal :)
    Wenn's nicht klappt, nutze dies und bau dir einen Wrapper, sollte einfacher sein.

    Multithreaded 7-zip compatible file archiver

    github.com/conor42/Radyx


    The library uses a parallel buffered radix matchfinder to enable multithreaded compression without splitting the input into large chunks or duplicating the entire matchfinder/encoder combination for every 2 threads.

    Cloud Computer? Nein Danke! Das ist nur ein weiterer Schritt zur totalen Überwachung.
    „Wer die Freiheit aufgibt, um Sicherheit zu gewinnen, wird am Ende beides verlieren.“
    Benjamin Franklin
    So, also für meine Zwecke hat das jetzt soweit erstmal funktioniert. Ich habe jetzt ohne Lib den Aufbau der zip-Datei selber nachgebaut. Natürlich nur genau die Teile, die ich brauche. Alles wäre viel zu viel, da hat zip noch echt viele Möglichkeiten. Das ist auch erstmal nur der erste Entwurf, der ist noch nicht optimiert. Aber trotzdem erstelle ich die zip-Datei jetzt dank multicore 3 mal so schnell. Genau wie ich mir das gedacht habe kann ich die Dateien einzeln komprimieren und dann "nur" noch zusammen fügen. Hier mal die Basis:

    C#-Quellcode

    1. public static class Crc32Helper {
    2. public static uint UpdateCrc32(uint crc32, byte[] buffer, int offset, int length) {
    3. crc32 ^= uint.MaxValue;
    4. while (--length >= 0) {
    5. crc32 = (Crc32Helper.crcTable[(int)((crc32 ^ buffer[offset++]) & 255u)] ^ crc32 >> 8);
    6. }
    7. crc32 ^= uint.MaxValue;
    8. return crc32;
    9. }
    10. private static readonly uint[] crcTable = new uint[] { 0u, 1996959894u, 3993919788u, 2567524794u, 124634137u, 1886057615u, 3915621685u, 2657392035u, 249268274u, 2044508324u, 3772115230u, 2547177864u, 162941995u, 2125561021u, 3887607047u, 2428444049u, 498536548u, 1789927666u, 4089016648u, 2227061214u, 450548861u, 1843258603u, 4107580753u, 2211677639u, 325883990u, 1684777152u, 4251122042u, 2321926636u, 335633487u, 1661365465u, 4195302755u, 2366115317u, 997073096u, 1281953886u, 3579855332u, 2724688242u, 1006888145u, 1258607687u, 3524101629u, 2768942443u, 901097722u, 1119000684u, 3686517206u, 2898065728u, 853044451u, 1172266101u, 3705015759u, 2882616665u, 651767980u, 1373503546u, 3369554304u, 3218104598u, 565507253u, 1454621731u, 3485111705u, 3099436303u, 671266974u, 1594198024u, 3322730930u, 2970347812u, 795835527u, 1483230225u, 3244367275u, 3060149565u, 1994146192u, 31158534u, 2563907772u, 4023717930u, 1907459465u, 112637215u, 2680153253u, 3904427059u, 2013776290u, 251722036u, 2517215374u, 3775830040u, 2137656763u, 141376813u, 2439277719u, 3865271297u, 1802195444u, 476864866u, 2238001368u, 4066508878u, 1812370925u, 453092731u, 2181625025u, 4111451223u, 1706088902u, 314042704u, 2344532202u, 4240017532u, 1658658271u, 366619977u, 2362670323u, 4224994405u, 1303535960u, 984961486u, 2747007092u, 3569037538u, 1256170817u, 1037604311u, 2765210733u, 3554079995u, 1131014506u, 879679996u, 2909243462u, 3663771856u, 1141124467u, 855842277u, 2852801631u, 3708648649u, 1342533948u, 654459306u, 3188396048u, 3373015174u, 1466479909u, 544179635u, 3110523913u, 3462522015u, 1591671054u, 702138776u, 2966460450u, 3352799412u, 1504918807u, 783551873u, 3082640443u, 3233442989u, 3988292384u, 2596254646u, 62317068u, 1957810842u, 3939845945u, 2647816111u, 81470997u, 1943803523u, 3814918930u, 2489596804u, 225274430u, 2053790376u, 3826175755u, 2466906013u, 167816743u, 2097651377u, 4027552580u, 2265490386u, 503444072u, 1762050814u, 4150417245u, 2154129355u, 426522225u, 1852507879u, 4275313526u, 2312317920u, 282753626u, 1742555852u, 4189708143u, 2394877945u, 397917763u, 1622183637u, 3604390888u, 2714866558u, 953729732u, 1340076626u, 3518719985u, 2797360999u, 1068828381u, 1219638859u, 3624741850u, 2936675148u, 906185462u, 1090812512u, 3747672003u, 2825379669u, 829329135u, 1181335161u, 3412177804u, 3160834842u, 628085408u, 1382605366u, 3423369109u, 3138078467u, 570562233u, 1426400815u, 3317316542u, 2998733608u, 733239954u, 1555261956u, 3268935591u, 3050360625u, 752459403u, 1541320221u, 2607071920u, 3965973030u, 1969922972u, 40735498u, 2617837225u, 3943577151u, 1913087877u, 83908371u, 2512341634u, 3803740692u, 2075208622u, 213261112u, 2463272603u, 3855990285u, 2094854071u, 198958881u, 2262029012u, 4057260610u, 1759359992u, 534414190u, 2176718541u, 4139329115u, 1873836001u, 414664567u, 2282248934u, 4279200368u, 1711684554u, 285281116u, 2405801727u, 4167216745u, 1634467795u, 376229701u, 2685067896u, 3608007406u, 1308918612u, 956543938u, 2808555105u, 3495958263u, 1231636301u, 1047427035u, 2932959818u, 3654703836u, 1088359270u, 936918000u, 2847714899u, 3736837829u, 1202900863u, 817233897u, 3183342108u, 3401237130u, 1404277552u, 615818150u, 3134207493u, 3453421203u, 1423857449u, 601450431u, 3009837614u, 3294710456u, 1567103746u, 711928724u, 3020668471u, 3272380065u, 1510334235u, 755167117u };
    11. }
    12. public class ZipEntryStream : Stream {
    13. public override bool CanRead => false;
    14. public override bool CanSeek => false;
    15. public override bool CanWrite => this.deflateStream.CanWrite;
    16. public override long Length => throw new NotSupportedException();
    17. public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }
    18. private Stream baseStream;
    19. private DeflateStream deflateStream;
    20. private long uncompressedBytesWritten;
    21. private long payloadPosition;
    22. private uint crcChecksum;
    23. public ZipEntryStream(Stream stream, string entryName) {
    24. this.baseStream = stream;
    25. using (BinaryWriter writer = new BinaryWriter(stream, new UTF8Encoding(false, true), true)) {
    26. writer.Write(0x04_03_4B_50u);
    27. writer.Write((ushort)45); //Version needed to extract (minimum)
    28. writer.Write((ushort)0); //General purpose bit flag
    29. writer.Write((ushort)8); //Compression method
    30. writer.Write(ToMsDosDateTime(DateTime.Now)); //File last modification time and date
    31. writer.Write((uint)0); //CRC-32
    32. writer.Write(-1); //Compressed size
    33. writer.Write(-1); //Uncompressed size
    34. byte[] nameBytes = Encoding.UTF8.GetBytes(entryName);
    35. writer.Write((ushort)nameBytes.Length); //File name length
    36. writer.Write((ushort)20); //Extra field length
    37. writer.Write(nameBytes);
    38. writer.Write(0x00_10_00_01);
    39. writer.Write((ulong)0); //Compressed size
    40. writer.Write((ulong)0); //Uncompressed size
    41. this.payloadPosition = stream.Position;
    42. }
    43. this.deflateStream = new DeflateStream(stream, CompressionLevel.Optimal, true);
    44. }
    45. public override void Close() {
    46. base.Close();
    47. this.deflateStream.Dispose();
    48. using (BinaryWriter writer = new BinaryWriter(this.baseStream, new UTF8Encoding(false, true), true)) {
    49. long currentPosition = this.baseStream.Position;
    50. this.baseStream.Position = 14;
    51. writer.Write(crcChecksum); //CRC-32
    52. this.baseStream.Position = this.payloadPosition - 16;
    53. writer.Write(this.uncompressedBytesWritten); //Compressed size
    54. writer.Write(currentPosition - this.payloadPosition); //Uncompressed size
    55. this.baseStream.Position = currentPosition;
    56. }
    57. }
    58. private static uint ToMsDosDateTime(DateTime dateTime) => ((((uint)dateTime.Second) / 2) & 0x1F) | ((((uint)dateTime.Minute) & 0x3F) << 5) | ((((uint)dateTime.Hour) & 0x1F) << 11) | ((((uint)dateTime.Day) & 0x1F) << 16) | ((((uint)dateTime.Month) & 0xF) << 21) | ((((uint)(dateTime.Year - 1980)) & 0x7F) << 25);
    59. public override void Flush() => this.deflateStream.Flush();
    60. public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException();
    61. public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
    62. public override void SetLength(long value) => throw new NotSupportedException();
    63. public override void Write(byte[] buffer, int offset, int count) {
    64. this.crcChecksum = Crc32Helper.UpdateCrc32(this.crcChecksum, buffer, offset, count);
    65. this.deflateStream.Write(buffer, offset, count);
    66. this.uncompressedBytesWritten += count;
    67. }
    68. }
    69. public class ZipArchivStream : IDisposable {
    70. private Stream baseStream;
    71. private MemoryStream centralDirectoryFileHeaderBuffer = new MemoryStream();
    72. private ushort centralDirectoryCount;
    73. private bool isDisposed;
    74. public ZipArchivStream(Stream stream) => this.baseStream = stream;
    75. public void Add(Stream zipEntryStream) {
    76. lock (this) {
    77. if (!this.isDisposed) {
    78. uint localFileHeaderOffset = (uint)this.baseStream.Position;
    79. this.centralDirectoryFileHeaderBuffer.Write(new byte[] { 0x50, 0x4B, 0x01, 0x02, 0x33, 0x00 }, 0, 6);
    80. zipEntryStream.Position = 4;
    81. using (BinaryReader reader = new BinaryReader(zipEntryStream, new UTF8Encoding(false, true), true)) {
    82. using (BinaryWriter writer = new BinaryWriter(centralDirectoryFileHeaderBuffer, new UTF8Encoding(false, true), true)) {
    83. writer.Write(reader.ReadUInt64());
    84. writer.Write(reader.ReadUInt64());
    85. writer.Write(reader.ReadUInt32());
    86. writer.Write(reader.ReadUInt16());
    87. ushort fileNameLength = reader.ReadUInt16();
    88. writer.Write(fileNameLength);
    89. writer.Write(reader.ReadUInt16());
    90. writer.Write((ushort)0);
    91. writer.Write((ushort)0);
    92. writer.Write((ushort)0);
    93. writer.Write((uint)0);
    94. writer.Write(localFileHeaderOffset);
    95. byte[] fileNameAndExtraField = reader.ReadBytes(fileNameLength + 20);
    96. writer.Write(fileNameAndExtraField, 0, fileNameAndExtraField.Length);
    97. }
    98. }
    99. zipEntryStream.Position = 0;
    100. zipEntryStream.CopyTo(this.baseStream);
    101. this.centralDirectoryCount++;
    102. }
    103. }
    104. }
    105. public void Dispose() {
    106. lock (this) {
    107. if (!this.isDisposed) {
    108. this.isDisposed = true;
    109. uint centralDirectoryStartIndex = (uint)this.baseStream.Position;
    110. this.centralDirectoryFileHeaderBuffer.Position = 0;
    111. this.centralDirectoryFileHeaderBuffer.CopyTo(this.baseStream);
    112. this.baseStream.Write(new byte[] { 0x50, 0x4B, 0x05, 0x06, 0x00, 0x00, 0x00, 0x00 }, 0, 8);
    113. using (BinaryWriter writer = new BinaryWriter(this.baseStream, new UTF8Encoding(false, true), true)) {
    114. writer.Write(centralDirectoryCount);
    115. writer.Write(centralDirectoryCount);
    116. writer.Write((uint)this.centralDirectoryFileHeaderBuffer.Length);
    117. writer.Write(centralDirectoryStartIndex);
    118. writer.Write((ushort)0);
    119. }
    120. this.centralDirectoryFileHeaderBuffer.Dispose();
    121. }
    122. }
    123. }
    124. }


    Und so benutze ich das ganze dann als Beispiel:

    C#-Quellcode

    1. using (FileStream fs = new FileStream(".\\Test.zip", FileMode.Create, FileAccess.ReadWrite, FileShare.Read)) {
    2. using (ZipArchivStream zas = new ZipArchivStream(fs)) {
    3. Parallel.ForEach(data.Keys, (file) => {
    4. using (MemoryStream ms = new MemoryStream()) {
    5. using (ZipEntryStream zes = new ZipEntryStream(ms, file)) {
    6. zes.Write(data[file], 0, data[file].Length);
    7. }
    8. zas.Add(ms);
    9. }
    10. });
    11. }
    12. }