mirror of
https://github.com/adamhathcock/sharpcompress.git
synced 2026-11-15 17:02:03 +00:00
Merge remote-tracking branch 'origin/master' into perf/rar-profile-pooling
This commit is contained in:
@@ -3,6 +3,7 @@ using System.Buffers;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.IO;
|
||||
|
||||
namespace SharpCompress.Compressors.LZMA;
|
||||
@@ -259,7 +260,7 @@ internal sealed class Lzma2EncoderStream : Stream
|
||||
_bufferPosition = 0;
|
||||
|
||||
// Try compressing the data
|
||||
(byte[] Buffer, int Length) compressed;
|
||||
PooledMemoryStream compressed;
|
||||
try
|
||||
{
|
||||
compressed = CompressBlock(_buffer, uncompressedSize);
|
||||
@@ -271,13 +272,14 @@ internal sealed class Lzma2EncoderStream : Stream
|
||||
return;
|
||||
}
|
||||
|
||||
using var compressedScope = compressed;
|
||||
// Check if compressed output fits in a single chunk and is actually smaller
|
||||
if (
|
||||
compressed.Length <= MAX_COMPRESSED_CHUNK_SIZE
|
||||
&& compressed.Length < uncompressedData.Length
|
||||
)
|
||||
{
|
||||
WriteCompressedChunk(uncompressedData.Length, compressed.Buffer, compressed.Length);
|
||||
WriteCompressedChunk(uncompressedData.Length, compressed, (int)compressed.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -296,7 +298,7 @@ internal sealed class Lzma2EncoderStream : Stream
|
||||
var uncompressedData = _buffer.AsMemory(0, uncompressedSize);
|
||||
_bufferPosition = 0;
|
||||
|
||||
(byte[] Buffer, int Length) compressed;
|
||||
PooledMemoryStream compressed;
|
||||
try
|
||||
{
|
||||
compressed = CompressBlock(_buffer, uncompressedSize);
|
||||
@@ -308,6 +310,7 @@ internal sealed class Lzma2EncoderStream : Stream
|
||||
return;
|
||||
}
|
||||
|
||||
using var compressedScope = compressed;
|
||||
if (
|
||||
compressed.Length <= MAX_COMPRESSED_CHUNK_SIZE
|
||||
&& compressed.Length < uncompressedData.Length
|
||||
@@ -315,8 +318,8 @@ internal sealed class Lzma2EncoderStream : Stream
|
||||
{
|
||||
await WriteCompressedChunkAsync(
|
||||
uncompressedData.Length,
|
||||
compressed.Buffer,
|
||||
compressed.Length,
|
||||
compressed,
|
||||
(int)compressed.Length,
|
||||
cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
@@ -328,23 +331,29 @@ internal sealed class Lzma2EncoderStream : Stream
|
||||
}
|
||||
}
|
||||
|
||||
private (byte[] Buffer, int Length) CompressBlock(byte[] data, int length)
|
||||
private PooledMemoryStream CompressBlock(byte[] data, int length)
|
||||
{
|
||||
using var inputMs = new MemoryStream(data, 0, length, writable: false);
|
||||
using var outputMs = new PooledMemoryStream();
|
||||
|
||||
_encoder.Code(inputMs, outputMs, length, -1, null);
|
||||
|
||||
var fullCompressed = outputMs.ToArray();
|
||||
|
||||
// The LZMA range encoder flush writes trailing bytes the decoder doesn't consume.
|
||||
// Trial-decode to find the exact byte count the decoder needs, so the LZMA2
|
||||
// chunk header reports a compressed size that matches what the decoder reads.
|
||||
var consumed = FindConsumedBytes(fullCompressed, fullCompressed.Length, length);
|
||||
return (fullCompressed, consumed);
|
||||
var outputMs = new PooledMemoryStream();
|
||||
try
|
||||
{
|
||||
_encoder.Code(inputMs, outputMs, length, -1, null);
|
||||
// Trial-decode to exclude trailing range-encoder bytes from the chunk size.
|
||||
outputMs.Position = 0;
|
||||
var consumed = FindConsumedBytes(outputMs, length);
|
||||
outputMs.SetLength(consumed);
|
||||
outputMs.Position = 0;
|
||||
// The caller owns the pooled output until the chunk write completes.
|
||||
return outputMs;
|
||||
}
|
||||
catch
|
||||
{
|
||||
outputMs.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private int FindConsumedBytes(byte[] compressedData, int compressedSize, int uncompressedSize)
|
||||
private int FindConsumedBytes(PooledMemoryStream input, int uncompressedSize)
|
||||
{
|
||||
// Build 5-byte LZMA property header: [pb/lp/lc byte] [dictSize as LE int32]
|
||||
Span<byte> props = stackalloc byte[5];
|
||||
@@ -357,8 +366,7 @@ internal sealed class Lzma2EncoderStream : Stream
|
||||
using var decoder = new Decoder();
|
||||
decoder.SetDecoderProperties(props);
|
||||
|
||||
using var input = new MemoryStream(compressedData, 0, compressedSize, writable: false);
|
||||
decoder.Code(input, Stream.Null, compressedSize, uncompressedSize, null);
|
||||
decoder.Code(input, Stream.Null, input.Length, uncompressedSize, null);
|
||||
|
||||
return (int)input.Position;
|
||||
}
|
||||
@@ -369,7 +377,7 @@ internal sealed class Lzma2EncoderStream : Stream
|
||||
/// </summary>
|
||||
private void WriteCompressedChunk(
|
||||
int uncompressedSize,
|
||||
byte[] compressedData,
|
||||
PooledMemoryStream compressedData,
|
||||
int compressedSize
|
||||
)
|
||||
{
|
||||
@@ -393,12 +401,12 @@ internal sealed class Lzma2EncoderStream : Stream
|
||||
// 0xE0 (>= 0xC0) requires properties byte
|
||||
_output.WriteByte(_lzmaPropertiesByte);
|
||||
|
||||
_output.Write(compressedData, 0, compressedSize);
|
||||
compressedData.WriteTo(_output);
|
||||
}
|
||||
|
||||
private async ValueTask WriteCompressedChunkAsync(
|
||||
int uncompressedSize,
|
||||
byte[] compressedData,
|
||||
PooledMemoryStream compressedData,
|
||||
int compressedSize,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
@@ -423,8 +431,8 @@ internal sealed class Lzma2EncoderStream : Stream
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(header);
|
||||
}
|
||||
await _output
|
||||
.WriteAsync(compressedData, 0, compressedSize, cancellationToken)
|
||||
await compressedData
|
||||
.CopyToAsync(_output, Constants.BufferSize, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
||||
@@ -465,8 +465,9 @@ public partial class LzmaStream
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (_encoder != null)
|
||||
{
|
||||
using var input = new ReadOnlyMemoryStream(buffer);
|
||||
_position = await _encoder
|
||||
.CodeAsync(new MemoryStream(buffer.ToArray()), false, cancellationToken)
|
||||
.CodeAsync(input, false, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -412,13 +412,9 @@ public class PpmdStream : Stream, IAsyncDisposable
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (_compress)
|
||||
{
|
||||
using var input = new ReadOnlyMemoryStream(buffer);
|
||||
await _model
|
||||
.EncodeBlockAsync(
|
||||
_stream,
|
||||
new MemoryStream(buffer.ToArray()),
|
||||
false,
|
||||
cancellationToken
|
||||
)
|
||||
.EncodeBlockAsync(_stream, input, false, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
99
src/SharpCompress/IO/ReadOnlyMemoryStream.cs
Normal file
99
src/SharpCompress/IO/ReadOnlyMemoryStream.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SharpCompress.IO;
|
||||
|
||||
// Borrows memory for the lifetime of an encoder call; never copies or owns the backing storage.
|
||||
internal sealed class ReadOnlyMemoryStream(ReadOnlyMemory<byte> memory) : Stream
|
||||
{
|
||||
private ReadOnlyMemory<byte> _memory = memory;
|
||||
private int _position;
|
||||
private bool _disposed;
|
||||
|
||||
public override bool CanRead => !_disposed;
|
||||
public override bool CanSeek => false;
|
||||
public override bool CanWrite => false;
|
||||
public override long Length =>
|
||||
!_disposed
|
||||
? _memory.Length
|
||||
: throw new ObjectDisposedException(nameof(ReadOnlyMemoryStream));
|
||||
public override long Position
|
||||
{
|
||||
get =>
|
||||
!_disposed
|
||||
? _position
|
||||
: throw new ObjectDisposedException(nameof(ReadOnlyMemoryStream));
|
||||
set => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
ThrowHelper.ThrowIfNull(buffer, nameof(buffer));
|
||||
if (offset < 0 || count < 0 || offset > buffer.Length - count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(count), "Invalid buffer range.");
|
||||
}
|
||||
return ReadCore(buffer.AsSpan(offset, count));
|
||||
}
|
||||
|
||||
private int ReadCore(Span<byte> buffer)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(ReadOnlyMemoryStream));
|
||||
}
|
||||
var count = Math.Min(buffer.Length, _memory.Length - _position);
|
||||
_memory.Span.Slice(_position, count).CopyTo(buffer);
|
||||
_position += count;
|
||||
return count;
|
||||
}
|
||||
|
||||
public override int ReadByte()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(ReadOnlyMemoryStream));
|
||||
}
|
||||
return _position < _memory.Length ? _memory.Span[_position++] : -1;
|
||||
}
|
||||
|
||||
public override Task<int> ReadAsync(
|
||||
byte[] buffer,
|
||||
int offset,
|
||||
int count,
|
||||
CancellationToken cancellationToken
|
||||
) =>
|
||||
cancellationToken.IsCancellationRequested
|
||||
? Task.FromCanceled<int>(cancellationToken)
|
||||
: Task.FromResult(Read(buffer, offset, count));
|
||||
|
||||
#if !LEGACY_DOTNET
|
||||
public override int Read(Span<byte> buffer) => ReadCore(buffer);
|
||||
|
||||
public override ValueTask<int> ReadAsync(
|
||||
Memory<byte> buffer,
|
||||
CancellationToken cancellationToken = default
|
||||
) =>
|
||||
cancellationToken.IsCancellationRequested
|
||||
? ValueTask.FromCanceled<int>(cancellationToken)
|
||||
: ValueTask.FromResult(ReadCore(buffer.Span));
|
||||
#endif
|
||||
|
||||
public override void Flush() { }
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
|
||||
|
||||
public override void SetLength(long value) => throw new NotSupportedException();
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count) =>
|
||||
throw new NotSupportedException();
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
_disposed = true;
|
||||
_memory = default;
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
@@ -103,21 +103,18 @@ public partial class SevenZipWriter
|
||||
// Write the encoded header to the output
|
||||
var headerStartPos = output.Position;
|
||||
encodedHeaderStream.Position = 0;
|
||||
await encodedHeaderStream.CopyToAsync(output).ConfigureAwait(false);
|
||||
|
||||
// Compute CRC of the encoded header
|
||||
var headerCrc = Crc32Stream.Compute(
|
||||
Crc32Stream.DEFAULT_POLYNOMIAL,
|
||||
Crc32Stream.DEFAULT_SEED,
|
||||
encodedHeaderStream.GetBuffer().AsSpan(0, (int)encodedHeaderStream.Length)
|
||||
);
|
||||
// Compute the CRC during the copy without materializing the pooled header.
|
||||
using var crcStream = new Crc32Stream(output);
|
||||
await encodedHeaderStream
|
||||
.CopyToAsync(crcStream, Constants.BufferSize, CancellationToken.None)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
// Back-patch signature header
|
||||
var nextHeaderOffset = (ulong)(headerStartPos - SevenZipSignatureHeaderWriter.HeaderSize);
|
||||
var nextHeaderSize = (ulong)encodedHeaderStream.Length;
|
||||
|
||||
await SevenZipSignatureHeaderWriter
|
||||
.WriteFinalAsync(output, nextHeaderOffset, nextHeaderSize, headerCrc)
|
||||
.WriteFinalAsync(output, nextHeaderOffset, nextHeaderSize, crcStream.Crc)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
// Seek to end
|
||||
@@ -134,21 +131,18 @@ public partial class SevenZipWriter
|
||||
// Write raw header directly
|
||||
var headerStartPos = output.Position;
|
||||
rawHeaderStream.Position = 0;
|
||||
await rawHeaderStream.CopyToAsync(output).ConfigureAwait(false);
|
||||
|
||||
// Compute CRC of the raw header
|
||||
var headerCrc = Crc32Stream.Compute(
|
||||
Crc32Stream.DEFAULT_POLYNOMIAL,
|
||||
Crc32Stream.DEFAULT_SEED,
|
||||
rawHeaderStream.GetBuffer().AsSpan(0, (int)rawHeaderStream.Length)
|
||||
);
|
||||
// Compute the CRC during the copy without materializing the pooled header.
|
||||
using var crcStream = new Crc32Stream(output);
|
||||
await rawHeaderStream
|
||||
.CopyToAsync(crcStream, Constants.BufferSize, CancellationToken.None)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
// Back-patch signature header
|
||||
var nextHeaderOffset = (ulong)(headerStartPos - SevenZipSignatureHeaderWriter.HeaderSize);
|
||||
var nextHeaderSize = (ulong)rawHeaderStream.Length;
|
||||
|
||||
await SevenZipSignatureHeaderWriter
|
||||
.WriteFinalAsync(output, nextHeaderOffset, nextHeaderSize, headerCrc)
|
||||
.WriteFinalAsync(output, nextHeaderOffset, nextHeaderSize, crcStream.Crc)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
// Seek to end
|
||||
|
||||
Reference in New Issue
Block a user