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/7zip-profile-pooling
# Conflicts: # src/SharpCompress/Compressors/LZMA/Lzma2EncoderStream.cs # src/SharpCompress/Compressors/LZMA/LzmaStream.Async.cs # src/SharpCompress/IO/ReadOnlyMemoryStream.cs
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;
|
||||
@@ -339,11 +340,12 @@ internal sealed class Lzma2EncoderStream : Stream
|
||||
try
|
||||
{
|
||||
_encoder.Code(inputMs, outputMs, length, -1, null);
|
||||
// Trial-decode to exclude trailing range-encoder bytes from the chunk size.
|
||||
outputMs.Position = 0;
|
||||
// Trial decoding excludes range-encoder flush bytes from the chunk size.
|
||||
var consumed = FindConsumedBytes(outputMs, (int)outputMs.Length, length);
|
||||
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
|
||||
@@ -356,7 +358,7 @@ internal sealed class Lzma2EncoderStream : Stream
|
||||
}
|
||||
}
|
||||
|
||||
private int FindConsumedBytes(Stream input, 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];
|
||||
@@ -369,7 +371,7 @@ internal sealed class Lzma2EncoderStream : Stream
|
||||
var decoder = _trialDecoder ??= new Decoder();
|
||||
decoder.SetDecoderProperties(props);
|
||||
|
||||
decoder.Code(input, Stream.Null, compressedSize, uncompressedSize, null);
|
||||
decoder.Code(input, Stream.Null, input.Length, uncompressedSize, null);
|
||||
|
||||
return (int)input.Position;
|
||||
}
|
||||
@@ -434,7 +436,14 @@ internal sealed class Lzma2EncoderStream : Stream
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(header);
|
||||
}
|
||||
await compressedData.CopyToAsync(_output, 65536, cancellationToken).ConfigureAwait(false);
|
||||
// Compressed chunks fit 64 KiB; avoid an 80 KiB request rounding to 128 KiB.
|
||||
await compressedData
|
||||
.CopyToAsync(
|
||||
_output,
|
||||
Math.Min(Constants.BufferSize, MAX_COMPRESSED_CHUNK_SIZE),
|
||||
cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -465,7 +465,7 @@ public partial class LzmaStream
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (_encoder != null)
|
||||
{
|
||||
using var input = new SharpCompress.IO.ReadOnlyMemoryStream(buffer);
|
||||
using var input = new ReadOnlyMemoryStream(buffer);
|
||||
_position = await _encoder
|
||||
.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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,55 +5,80 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace SharpCompress.IO;
|
||||
|
||||
// Borrows caller memory only until the awaited encoder operation completes.
|
||||
// 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 int position;
|
||||
public override bool CanRead => true;
|
||||
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 => memory.Length;
|
||||
public override long Length =>
|
||||
!_disposed
|
||||
? _memory.Length
|
||||
: throw new ObjectDisposedException(nameof(ReadOnlyMemoryStream));
|
||||
public override long Position
|
||||
{
|
||||
get => position;
|
||||
get =>
|
||||
!_disposed
|
||||
? _position
|
||||
: throw new ObjectDisposedException(nameof(ReadOnlyMemoryStream));
|
||||
set => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
private int ReadMemory(Span<byte> buffer)
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
var count = Math.Min(buffer.Length, memory.Length - position);
|
||||
memory.Span.Slice(position, count).CopyTo(buffer);
|
||||
position += 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 Read(byte[] buffer, int offset, int count) =>
|
||||
ReadMemory(buffer.AsSpan(offset, count));
|
||||
|
||||
public override int ReadByte() => position < memory.Length ? memory.Span[position++] : -1;
|
||||
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.ThrowIfCancellationRequested();
|
||||
return Task.FromResult(Read(buffer, offset, count));
|
||||
}
|
||||
) =>
|
||||
cancellationToken.IsCancellationRequested
|
||||
? Task.FromCanceled<int>(cancellationToken)
|
||||
: Task.FromResult(Read(buffer, offset, count));
|
||||
|
||||
#if !LEGACY_DOTNET
|
||||
public override int Read(Span<byte> buffer) => ReadMemory(buffer);
|
||||
public override int Read(Span<byte> buffer) => ReadCore(buffer);
|
||||
|
||||
public override ValueTask<int> ReadAsync(
|
||||
Memory<byte> buffer,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return new ValueTask<int>(ReadMemory(buffer.Span));
|
||||
}
|
||||
) =>
|
||||
cancellationToken.IsCancellationRequested
|
||||
? ValueTask.FromCanceled<int>(cancellationToken)
|
||||
: ValueTask.FromResult(ReadCore(buffer.Span));
|
||||
#endif
|
||||
|
||||
public override void Flush() { }
|
||||
@@ -64,4 +89,11 @@ internal sealed class ReadOnlyMemoryStream(ReadOnlyMemory<byte> memory) : Stream
|
||||
|
||||
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