Skip to content

Commit

Permalink
bug fix
Browse files Browse the repository at this point in the history
  • Loading branch information
kingsznhone committed Sep 19, 2024
1 parent 34a5432 commit c93d56a
Show file tree
Hide file tree
Showing 12 changed files with 110 additions and 53 deletions.
46 changes: 33 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
# FastLZMA2Net

Fast LZMA2 Wrapper for .NET

⚠️Library is beta test⚠️

⚠️API may have breaking change⚠️
Fast LZMA2 Compression algorithm Wrapper for .NET

[Change Log](ChangeLog.md)

With respect to [Fast LZMA2](https://github.com/conor42/fast-lzma2)
With respect to [Fast LZMA2 repo](https://github.com/conor42/fast-lzma2)

![](./readme/benchmark.png)

Expand Down Expand Up @@ -45,7 +41,7 @@ byte[] decompressed = FL2.Decompress(compressed);

When you have many small file, consider using context to avoid alloc overhead

```c#
``` c#
// Context compression, context can be reuse.
Compressor compressor = new(0) { CompressLevel = 10 };
compressed = compressor.Compress(origin);
Expand Down Expand Up @@ -95,14 +91,13 @@ using (MemoryStream recoveryStream = new MemoryStream())

### Large file or data (>2GB)

When processing Large file, It is not acceptable reading all data into memory.
dotnet byte array have size limit <2GB

And .NET array have size limit <2GB
When processing Large file, It is not acceptable reading all data into memory.

It is recommended to using DFA(direct file access) streaming.

Have tested on a 7GB file.

**Streaming Compression**
```c#
//large file streaming compression using Direct file access(>2GB)
using (FileStream compressedFile = File.OpenWrite(CompressedFilePath))
Expand All @@ -111,24 +106,28 @@ using (FileStream compressedFile = File.OpenWrite(CompressedFilePath))
{
using (FileStream sourceFile = File.OpenRead(SourceFilePath))
{
//DO NOT USE sourceFile.CopyTo(cs)
//DO NOT USE sourceFile.CopyTo(cs) while using block buffer.
// CopyTo() calls Write() inside, which terminate stream after 1 cycle.
long offset = 0;
while (offset < sourceFile.Length)
{
long remaining = sourceFile.Length - offset;
//64M buffer is recommended.
int bytesToWrite = (int)Math.Min(64 * 1024 * 1024, remaining);
sourceFile.Read(buffer, 0, bytesToWrite);
cs.Append(buffer, 0, bytesToWrite);
offset += bytesToWrite;
}
// make sure always use Flush() after all Append() complete
// Flush() add checksum to stream and finish streaming.
// Flush() add checksum to end and finish streaming operation.
cs.Flush();
}
}
}
```

**Streaming Decompression**
``` c#
//large file streaming decompress(>2GB)
using (FileStream recoveryStream = File.OpenWrite(DecompressedFilePath))
{
Expand All @@ -142,6 +141,27 @@ using (FileStream recoveryStream = File.OpenWrite(DecompressedFilePath))
}
```

### Finetune Parameter

``` c#
Compressor compressor = new(0) { CompressLevel = 10 };
compressor.SetParameter(FL2Parameter.FastLength, 48);
```

### Estimate Memory Usage

``` c#
Compressor compressor = new(0) { CompressLevel = 10 };
nuint size = FL2.EstimateCompressMemoryUsage(compressor.ContextPtr);
size = EstimateCompressMemoryUsage(compressionLevel=10,nbThreads=8)
```

### Find Decompressed Data Size

``` c#
nuint size = FL2.FindDecompressedSize(data);
```

# Bug report

Open an issue.
Expand Down
2 changes: 2 additions & 0 deletions src/Demo/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ private static async Task Main(string[] args)

// Context compression, context can be reuse.
Compressor compressor = new(0) { CompressLevel = 10 };
compressor.CompressLevel = 10;
FL2.EstimateCompressMemoryUsage(compressor.ContextPtr);
compressed = compressor.Compress(origin);
compressed = compressor.Compress(origin);
compressed = compressor.Compress(origin);
Expand Down
19 changes: 19 additions & 0 deletions src/FastLZMA2Net.Test/DStreamTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,25 @@ public void TestBlocked()
}
}

[TestMethod]
public async Task TestAsync()
{
byte[] origin = File.ReadAllBytes(@"Resources/dummy.raw");
byte[] compressed = FL2.CompressMT(origin, 10, 1);
byte[] buffer = new byte[64*1024*1024];
Memory<byte> memory = new Memory<byte>(buffer);
int bytesRead = 0;
using (MemoryStream ms = new MemoryStream(compressed))
{
using (DecompressStream ds = new DecompressStream(ms, nbThreads: 0, inBufferSize: 1024))
{
bytesRead = await ds.ReadAsync(memory);
}
}
byte[] recovered = buffer[0..bytesRead];
Assert.IsTrue(origin.SequenceEqual(recovered));
}

[TestMethod]
public async Task TestBlockedAsync()
{
Expand Down
7 changes: 3 additions & 4 deletions src/FastLZMA2Net.Test/FL2Test.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,11 @@ public SimpleTest()
[TestMethod]
public void Simple()
{
byte[] src = new byte[4096 * 1024];
new Random().NextBytes(src);
byte[] origin = File.ReadAllBytes(@"Resources/dummy.raw");

byte[] compressed = FL2.Compress(src, 10);
byte[] compressed = FL2.Compress(origin, 10);
byte[] decompressed = FL2.Decompress(compressed);
Assert.IsTrue(src.SequenceEqual(decompressed), "The byte arrays are not equal.");
Assert.IsTrue(origin.SequenceEqual(decompressed), "The byte arrays are not equal.");
}

[TestMethod]
Expand Down
2 changes: 2 additions & 0 deletions src/FastLZMA2Net/CompressStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public class CompressStream : Stream
private bool disposed = false;
private readonly Stream _innerStream;
private readonly nint _context;
public nint ContextPtr => _context;
public override bool CanRead => _innerStream != null && _innerStream.CanRead;
public override bool CanWrite => false;
public override bool CanSeek => false;
Expand Down Expand Up @@ -407,6 +408,7 @@ protected override void Dispose(bool disposing)
if (!disposed)
{
Flush();
_innerStream.Dispose();
if (disposing)
{
outputBufferHandle.Free();
Expand Down
28 changes: 15 additions & 13 deletions src/FastLZMA2Net/Compressor.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.IO.MemoryMappedFiles;
using System.Runtime.Versioning;

namespace FastLZMA2Net
{
Expand All @@ -7,19 +8,20 @@ namespace FastLZMA2Net
/// </summary>
public partial class Compressor : IDisposable
{
private readonly nint _CContext;
private bool disposed = false;
private bool disposedValue;
private readonly nint _context;
public nint ContextPtr => _context;

private bool disposed;

/// <summary>
/// Thread use of the context
/// </summary>
public uint ThreadCount => NativeMethods.FL2_getCCtxThreadCount(_CContext);
public uint ThreadCount => NativeMethods.FL2_getCCtxThreadCount(_context);

/// <summary>
/// Dictionary size property
/// </summary>
public byte DictSizeProperty => NativeMethods.FL2_getCCtxDictProp(_CContext);
public byte DictSizeProperty => NativeMethods.FL2_getCCtxDictProp(_context);

/// <summary>
/// Compress Level [1..10]
Expand Down Expand Up @@ -78,7 +80,7 @@ public int FastLength
/// <param name="compressLevel">default = 6</param>
public Compressor(uint nbThreads = 0, int compressLevel = 6)
{
_CContext = NativeMethods.FL2_createCCtxMt(nbThreads);
_context = NativeMethods.FL2_createCCtxMt(nbThreads);
CompressLevel = (int)compressLevel;
}

Expand Down Expand Up @@ -120,7 +122,7 @@ public byte[] Compress(byte[] src, int compressLevel)
ArgumentNullException.ThrowIfNull(src);

byte[] buffer = new byte[FL2.FindCompressBound(src)];
nuint code = NativeMethods.FL2_compressCCtx(_CContext, buffer, (nuint)buffer.Length, src, (nuint)src.Length, compressLevel);
nuint code = NativeMethods.FL2_compressCCtx(_context, buffer, (nuint)buffer.Length, src, (nuint)src.Length, compressLevel);
if (FL2Exception.IsError(code))
{
throw new FL2Exception(code);
Expand Down Expand Up @@ -153,7 +155,7 @@ public unsafe nuint Compress(string srcPath, string dstPath)
}
using (DirectFileAccessor accessorDst = new DirectFileAccessor(destFile.FullName, FileMode.OpenOrCreate, null, sourceFile.Length, MemoryMappedFileAccess.ReadWrite))
{
code = NativeMethods.FL2_compressCCtx(_CContext, accessorDst.mmPtr, code, accessorSrc.mmPtr, (nuint)sourceFile.Length, CompressLevel);
code = NativeMethods.FL2_compressCCtx(_context, accessorDst.mmPtr, code, accessorSrc.mmPtr, (nuint)sourceFile.Length, CompressLevel);
if (FL2Exception.IsError(code))
{
throw new FL2Exception(code);
Expand All @@ -177,7 +179,7 @@ public unsafe nuint Compress(string srcPath, string dstPath)
/// <exception cref="FL2Exception"></exception>
public nuint SetParameter(FL2Parameter param, nuint value)
{
nuint code = NativeMethods.FL2_CCtx_setParameter(_CContext, param, value);
nuint code = NativeMethods.FL2_CCtx_setParameter(_context, param, value);
if (FL2Exception.IsError(code))
{
throw new FL2Exception(code);
Expand All @@ -193,7 +195,7 @@ public nuint SetParameter(FL2Parameter param, nuint value)
/// <exception cref="FL2Exception"></exception>
public nuint GetParameter(FL2Parameter param)
{
var code = NativeMethods.FL2_CCtx_getParameter(_CContext, param);
var code = NativeMethods.FL2_CCtx_getParameter(_context, param);
if (FL2Exception.IsError(code))
{
throw new FL2Exception(code);
Expand All @@ -203,11 +205,11 @@ public nuint GetParameter(FL2Parameter param)

protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
if (!disposed)
{
if (disposing) { }
NativeMethods.FL2_freeCCtx(_CContext);
disposedValue = true;
NativeMethods.FL2_freeCCtx(_context);
disposed = true;
}
}

Expand Down
5 changes: 4 additions & 1 deletion src/FastLZMA2Net/DecompressStream.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Runtime.InteropServices;
using static System.Runtime.InteropServices.JavaScript.JSType;

namespace FastLZMA2Net
{
Expand All @@ -14,6 +15,7 @@ public class DecompressStream : Stream
private bool disposed;
private readonly Stream _innerStream;
private readonly nint _context;
public nint ContextPtr => _context;
public override bool CanRead => _innerStream != null && _innerStream.CanRead;
public override bool CanWrite => false;
public override bool CanSeek => false;
Expand Down Expand Up @@ -80,6 +82,7 @@ public DecompressStream(Stream srcStream, uint nbThreads = 0, int inBufferSize =
/// <param name="bufferSize">Default = 256M</param>
public override void CopyTo(Stream destination, int bufferSize = 256 * 1024 * 1024)
{
ArgumentNullException.ThrowIfNull(destination);
byte[] outBufferArray = new byte[bufferSize];
Span<byte> outBufferSpan = outBufferArray.AsSpan();
int bytesRead = 0;
Expand Down Expand Up @@ -139,7 +142,6 @@ public override Task<int> ReadAsync(byte[] buffer, int offset, int count, Cancel
/// <returns>How many bytes read.</returns>
public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
{
_innerStream.ReadAsync(new byte[10], 0, 0);
return new ValueTask<int>(DecompressCore(buffer.Span, cancellationToken));
}

Expand Down Expand Up @@ -271,6 +273,7 @@ protected override void Dispose(bool disposing)
{
if (!disposed)
{
_innerStream.Dispose();
if (disposing)
{
inputBufferHandle.Free();
Expand Down
18 changes: 10 additions & 8 deletions src/FastLZMA2Net/Decompressor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
/// </summary>
public partial class Decompressor : IDisposable
{
private readonly nint _DContext;
private readonly nint _context;
public nint ContextPtr => _context;
private bool disposedValue;

/// <summary>
/// Thread use of the context
/// </summary>
public uint ThreadCount => NativeMethods.FL2_getDCtxThreadCount(_DContext);
public uint ThreadCount => NativeMethods.FL2_getDCtxThreadCount(_context);

/// <summary>
/// Initialize new decompress context
Expand All @@ -21,11 +22,11 @@ public Decompressor(uint nbThreads = 0)
{
if (nbThreads == 1)
{
_DContext = NativeMethods.FL2_createDCtx();
_context = NativeMethods.FL2_createDCtx();
}
else
{
_DContext = NativeMethods.FL2_createDCtxMt(nbThreads);
_context = NativeMethods.FL2_createDCtxMt(nbThreads);
}
}

Expand All @@ -35,7 +36,7 @@ public Decompressor(uint nbThreads = 0)
/// <param name="prop">dictSizeProperty</param>
public void Init(byte prop)
{
NativeMethods.FL2_initDCtx(_DContext, prop);
NativeMethods.FL2_initDCtx(_context, prop);
}

/// <summary>
Expand All @@ -46,22 +47,23 @@ public void Init(byte prop)
/// <exception cref="FL2Exception"></exception>
public byte[] Decompress(byte[] data)
{
ArgumentNullException.ThrowIfNull(data);
nuint decompressedSize = FL2.FindDecompressedSize(data);
byte[] decompressed = new byte[decompressedSize];
nuint code = NativeMethods.FL2_decompressDCtx(_DContext, decompressed, decompressedSize, data, (nuint)data.Length);
nuint code = NativeMethods.FL2_decompressDCtx(_context, decompressed, decompressedSize, data, (nuint)data.Length);
if (FL2Exception.IsError(code))
{
throw new FL2Exception(code);
}
return decompressed;
return decompressed[0..(int)code];
}

protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing) { }
NativeMethods.FL2_freeDCtx(_DContext);
NativeMethods.FL2_freeDCtx(_context);
disposedValue = true;
}
}
Expand Down
Loading

0 comments on commit c93d56a

Please sign in to comment.