From cc84f6fee4587b0f46dacaaa8808151e5f0a5054 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Thu, 23 Oct 2025 11:43:21 +0100 Subject: [PATCH] more making rar faster --- .../Compressors/Rar/UnpackV1/Unpack.cs | 24 ++++-- .../Compressors/Rar/UnpackV1/UnpackUtility.cs | 3 +- .../Compressors/Rar/VM/BitInput.cs | 11 ++- src/SharpCompress/Compressors/Rar/VM/RarVM.cs | 73 +++++++++++-------- src/SharpCompress/NotNullExtensions.cs | 1 + tests/SharpCompress.Performance/Program.cs | 59 ++++++++------- 6 files changed, 103 insertions(+), 68 deletions(-) diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs index 1c8efaff..e1c89c67 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs @@ -13,7 +13,7 @@ using SharpCompress.Compressors.Rar.VM; namespace SharpCompress.Compressors.Rar.UnpackV1; -internal sealed partial class Unpack : BitInput, IRarUnpack, IDisposable +internal sealed partial class Unpack : BitInput, IRarUnpack { private readonly BitInput Inp; private bool disposed; @@ -32,6 +32,7 @@ internal sealed partial class Unpack : BitInput, IRarUnpack, IDisposable ArrayPool.Shared.Return(window); window = null; } + rarVM.Dispose(); disposed = true; } } @@ -752,19 +753,27 @@ internal sealed partial class Unpack : BitInput, IRarUnpack, IDisposable // System.out.println("copyString(" + length + ", " + distance + ")"); var destPtr = unpPtr - distance; + var safeZone = PackDef.MAXWINSIZE - 260; - // System.out.println(unpPtr+":"+distance); - if (destPtr >= 0 && destPtr < PackDef.MAXWINSIZE - 260 && unpPtr < PackDef.MAXWINSIZE - 260) + // Fast path: use Array.Copy for bulk operations when in safe zone + if (destPtr >= 0 && destPtr < safeZone && unpPtr < safeZone && distance >= length) { - window[unpPtr++] = window[destPtr++]; - - while (--length > 0) + // Non-overlapping copy: can use Array.Copy directly + Array.Copy(window, destPtr, window, unpPtr, length); + unpPtr += length; + } + else if (destPtr >= 0 && destPtr < safeZone && unpPtr < safeZone) + { + // Overlapping copy in safe zone: use byte-by-byte to handle self-referential copies + for (int i = 0; i < length; i++) { - window[unpPtr++] = window[destPtr++]; + window[unpPtr + i] = window[destPtr + i]; } + unpPtr += length; } else { + // Slow path with wraparound mask while (length-- != 0) { window[unpPtr] = window[destPtr++ & PackDef.MAXWINMASK]; @@ -1081,7 +1090,6 @@ internal sealed partial class Unpack : BitInput, IRarUnpack, IDisposable { Inp.InBuf[i] = vmCode[i]; } - rarVM.init(); int FiltPos; if ((firstByte & 0x80) != 0) diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/UnpackUtility.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/UnpackUtility.cs index 3a6f7c0c..e63831d5 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV1/UnpackUtility.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/UnpackUtility.cs @@ -91,8 +91,7 @@ internal static class UnpackUtility long M, N; - new Span(dec.DecodeNum).Clear(); // memset(Dec->DecodeNum,0,Size*sizeof(*Dec->DecodeNum)); - + Array.Clear(dec.DecodeNum,0, dec.DecodeNum.Length); for (i = 0; i < size; i++) { lenCount[lenTab[offset + i] & 0xF]++; diff --git a/src/SharpCompress/Compressors/Rar/VM/BitInput.cs b/src/SharpCompress/Compressors/Rar/VM/BitInput.cs index 0f753757..8cd82056 100644 --- a/src/SharpCompress/Compressors/Rar/VM/BitInput.cs +++ b/src/SharpCompress/Compressors/Rar/VM/BitInput.cs @@ -24,6 +24,7 @@ internal class BitInput : IDisposable } public bool ExternalBuffer; private byte[] _privateBuffer = ArrayPool.Shared.Rent(MAX_SIZE); + private bool _disposed; /// internal BitInput() => InBuf = _privateBuffer; @@ -92,5 +93,13 @@ internal class BitInput : IDisposable /// internal bool Overflow(int IncPtr) => (inAddr + IncPtr >= MAX_SIZE); - public virtual void Dispose() => ArrayPool.Shared.Return(_privateBuffer); + public virtual void Dispose() + { + if (_disposed) + { + return; + } + ArrayPool.Shared.Return(_privateBuffer); + _disposed = true; + } } diff --git a/src/SharpCompress/Compressors/Rar/VM/RarVM.cs b/src/SharpCompress/Compressors/Rar/VM/RarVM.cs index c535ebb1..c8199e7f 100644 --- a/src/SharpCompress/Compressors/Rar/VM/RarVM.cs +++ b/src/SharpCompress/Compressors/Rar/VM/RarVM.cs @@ -1,6 +1,7 @@ -#nullable disable + using System; +using System.Buffers; using System.Buffers.Binary; using System.Collections.Generic; @@ -16,7 +17,9 @@ internal sealed class RarVM : BitInput // Mem.set_Renamed(offset + 3, Byte.valueOf((sbyte) ((Utility.URShift(value_Renamed, 24)) & 0xff))); //} - internal byte[] Mem { get; private set; } + internal byte[] Mem => _memory.NotNull(); + + private byte[]? _memory = ArrayPool.Shared.Rent(VM_MEMSIZE + 4); public const int VM_MEMSIZE = 0x40000; @@ -40,11 +43,22 @@ internal sealed class RarVM : BitInput private int IP; - internal RarVM() => - //InitBlock(); - Mem = null; + internal RarVM() + { - internal void init() => Mem ??= new byte[VM_MEMSIZE + 4]; + } + + + public override void Dispose() + { + base.Dispose(); + if (_memory is null) + { + return; + } + ArrayPool.Shared.Return(_memory); + _memory = null; + } private bool IsVMMem(byte[] mem) => Mem == mem; @@ -1131,6 +1145,7 @@ internal sealed class RarVM : BitInput private void ExecuteStandardFilter(VMStandardFilters filterType) { + var mem = Mem; switch (filterType) { case VMStandardFilters.VMSF_E8: @@ -1149,7 +1164,7 @@ internal sealed class RarVM : BitInput ); for (var curPos = 0; curPos < dataSize - 4; ) { - var curByte = Mem[curPos++]; + var curByte = mem[curPos++]; if (curByte == 0xe8 || curByte == cmpByte2) { // #ifdef PRESENT_INT32 @@ -1165,19 +1180,19 @@ internal sealed class RarVM : BitInput // SET_VALUE(false,Data,Addr-Offset); // #else var offset = curPos + fileOffset; - long Addr = GetValue(false, Mem, curPos); + long Addr = GetValue(false, mem, curPos); if ((Addr & unchecked((int)0x80000000)) != 0) { if (((Addr + offset) & unchecked((int)0x80000000)) == 0) { - SetValue(false, Mem, curPos, (int)Addr + fileSize); + SetValue(false, mem, curPos, (int)Addr + fileSize); } } else { if (((Addr - fileSize) & unchecked((int)0x80000000)) != 0) { - SetValue(false, Mem, curPos, (int)(Addr - offset)); + SetValue(false, mem, curPos, (int)(Addr - offset)); } } @@ -1205,7 +1220,7 @@ internal sealed class RarVM : BitInput while (curPos < dataSize - 21) { - var Byte = (Mem[curPos] & 0x1f) - 0x10; + var Byte = (mem[curPos] & 0x1f) - 0x10; if (Byte >= 0) { var cmdMask = Masks[Byte]; @@ -1251,7 +1266,7 @@ internal sealed class RarVM : BitInput var channels = R[0] & unchecked((int)0xFFffFFff); var srcPos = 0; var border = (dataSize * 2) & unchecked((int)0xFFffFFff); - SetValue(false, Mem, VM_GLOBALMEMADDR + 0x20, dataSize); + SetValue(false, mem, VM_GLOBALMEMADDR + 0x20, dataSize); if (dataSize >= VM_GLOBALMEMADDR / 2) { break; @@ -1269,7 +1284,7 @@ internal sealed class RarVM : BitInput destPos += channels ) { - Mem[destPos] = (PrevByte = (byte)(PrevByte - Mem[srcPos++])); + mem[destPos] = (PrevByte = (byte)(PrevByte - mem[srcPos++])); } } } @@ -1284,7 +1299,7 @@ internal sealed class RarVM : BitInput var channels = 3; var srcPos = 0; var destDataPos = dataSize; - SetValue(false, Mem, VM_GLOBALMEMADDR + 0x20, dataSize); + SetValue(false, mem, VM_GLOBALMEMADDR + 0x20, dataSize); if (dataSize >= VM_GLOBALMEMADDR / 2 || posR < 0) { break; @@ -1300,8 +1315,8 @@ internal sealed class RarVM : BitInput if (upperPos >= 3) { var upperDataPos = destDataPos + upperPos; - var upperByte = Mem[upperDataPos] & 0xff; - var upperLeftByte = Mem[upperDataPos - 3] & 0xff; + var upperByte = mem[upperDataPos] & 0xff; + var upperLeftByte = mem[upperDataPos - 3] & 0xff; predicted = prevByte + upperByte - upperLeftByte; var pa = Math.Abs((int)(predicted - prevByte)); var pb = Math.Abs((int)(predicted - upperByte)); @@ -1327,15 +1342,15 @@ internal sealed class RarVM : BitInput predicted = prevByte; } - prevByte = ((predicted - Mem[srcPos++]) & 0xff) & 0xff; - Mem[destDataPos + i] = (byte)(prevByte & 0xff); + prevByte = ((predicted - mem[srcPos++]) & 0xff) & 0xff; + mem[destDataPos + i] = (byte)(prevByte & 0xff); } } for (int i = posR, border = dataSize - 2; i < border; i += 3) { - var G = Mem[destDataPos + i + 1]; - Mem[destDataPos + i] = (byte)(Mem[destDataPos + i] + G); - Mem[destDataPos + i + 2] = (byte)(Mem[destDataPos + i + 2] + G); + var G = mem[destDataPos + i + 1]; + mem[destDataPos + i] = (byte)(mem[destDataPos + i] + G); + mem[destDataPos + i + 2] = (byte)(mem[destDataPos + i + 2] + G); } } break; @@ -1348,7 +1363,7 @@ internal sealed class RarVM : BitInput var destDataPos = dataSize; //byte *SrcData=Mem,*DestData=SrcData+DataSize; - SetValue(false, Mem, VM_GLOBALMEMADDR + 0x20, dataSize); + SetValue(false, mem, VM_GLOBALMEMADDR + 0x20, dataSize); if (dataSize >= VM_GLOBALMEMADDR / 2) { break; @@ -1378,10 +1393,10 @@ internal sealed class RarVM : BitInput var predicted = (8 * prevByte) + (K1 * D1) + (K2 * D2) + (K3 * D3); predicted = Utility.URShift(predicted, 3) & 0xff; - long curByte = Mem[srcPos++]; + long curByte = mem[srcPos++]; predicted -= curByte; - Mem[destDataPos + i] = (byte)predicted; + mem[destDataPos + i] = (byte)predicted; prevDelta = (byte)(predicted - prevByte); //fix java byte @@ -1481,15 +1496,15 @@ internal sealed class RarVM : BitInput } while (srcPos < dataSize) { - var curByte = Mem[srcPos++]; - if (curByte == 2 && (curByte = Mem[srcPos++]) != 2) + var curByte = mem[srcPos++]; + if (curByte == 2 && (curByte = mem[srcPos++]) != 2) { curByte = (byte)(curByte - 32); } - Mem[destPos++] = curByte; + mem[destPos++] = curByte; } - SetValue(false, Mem, VM_GLOBALMEMADDR + 0x1c, destPos - dataSize); - SetValue(false, Mem, VM_GLOBALMEMADDR + 0x20, dataSize); + SetValue(false, mem, VM_GLOBALMEMADDR + 0x1c, destPos - dataSize); + SetValue(false, mem, VM_GLOBALMEMADDR + 0x20, dataSize); } break; } diff --git a/src/SharpCompress/NotNullExtensions.cs b/src/SharpCompress/NotNullExtensions.cs index 38e6d6d6..4fdd355c 100644 --- a/src/SharpCompress/NotNullExtensions.cs +++ b/src/SharpCompress/NotNullExtensions.cs @@ -68,6 +68,7 @@ internal static class NotNullExtensions } #endif + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string NotNullOrEmpty(this string obj, string name) { obj.NotNull(name); diff --git a/tests/SharpCompress.Performance/Program.cs b/tests/SharpCompress.Performance/Program.cs index c5a89165..812cc86a 100644 --- a/tests/SharpCompress.Performance/Program.cs +++ b/tests/SharpCompress.Performance/Program.cs @@ -16,37 +16,40 @@ var path = AppDomain.CurrentDomain.BaseDirectory.Substring(0, index); var SOLUTION_BASE_PATH = Path.GetDirectoryName(path) ?? throw new ArgumentNullException(); var TEST_ARCHIVES_PATH = Path.Combine(SOLUTION_BASE_PATH, "TestArchives", "Archives"); -using var _ = JetbrainsProfiler.Memory($"/Users/adam/temp/"); -//using var __ = JetbrainsProfiler.Cpu($"/Users/adam/temp/"); -var testArchives = new[] +//using var _ = JetbrainsProfiler.Memory($"/Users/adam/temp/"); +using (var __ = JetbrainsProfiler.Cpu($"/Users/adam/temp/")) { - // "Rar.Audio_program.rar" - "64bitstream.zip.7z", - //"TarWithSymlink.tar.gz" -}; -var arcs = testArchives.Select(a => Path.Combine(TEST_ARCHIVES_PATH, a)).ToArray(); - -for (int i = 0; i < 20; i++) -{ - using var found = ArchiveFactory.Open(arcs[0]); - foreach (var entry in found.Entries.Where(entry => !entry.IsDirectory)) + var testArchives = new[] { - Console.WriteLine($"Extracting {entry.Key}"); - using var entryStream = entry.OpenEntryStream(); - entryStream.CopyTo(Stream.Null); + "Rar.Audio_program.rar" + + //"64bitstream.zip.7z", + //"TarWithSymlink.tar.gz" + }; + var arcs = testArchives.Select(a => Path.Combine(TEST_ARCHIVES_PATH, a)).ToArray(); + + for (int i = 0; i < 50; i++) + { + using var found = ArchiveFactory.Open(arcs[0]); + foreach (var entry in found.Entries.Where(entry => !entry.IsDirectory)) + { + Console.WriteLine($"Extracting {entry.Key}"); + using var entryStream = entry.OpenEntryStream(); + entryStream.CopyTo(Stream.Null); + } + /*using var found = ReaderFactory.Open(arcs[0]); + while (found.MoveToNextEntry()) + { + var entry = found.Entry; + if (entry.IsDirectory) + continue; + + Console.WriteLine($"Extracting {entry.Key}"); + found.WriteEntryTo(Stream.Null); + }*/ } - /*using var found = ReaderFactory.Open(arcs[0]); - while (found.MoveToNextEntry()) - { - var entry = found.Entry; - if (entry.IsDirectory) - continue; - Console.WriteLine($"Extracting {entry.Key}"); - found.WriteEntryTo(Stream.Null); - }*/ + Console.WriteLine("Still running..."); } - -Console.WriteLine("Still running..."); -await Task.Delay(5000); +await Task.Delay(500);