Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
8c07524
Avoid unnecessary byte[] allocations
stephentoub Aug 19, 2022
53d24d0
Remove unnecessary use of FileStreamOptions
stephentoub Aug 19, 2022
c9dc0be
Clean up Dispose{Async} implementations
stephentoub Aug 19, 2022
4e1af20
Clean up unnecessary consts
stephentoub Aug 19, 2022
13f011b
Remove MemoryStream/Encoding.UTF8.GetBytes allocations, unnecessary a…
stephentoub Aug 19, 2022
8735a67
Avoid string allocations in ReadMagicAttribute
stephentoub Aug 19, 2022
f50edcf
Avoid allocation in WriteAsOctal
stephentoub Aug 19, 2022
29a90d3
Improve handling of octal
stephentoub Aug 19, 2022
e211457
Avoid allocation for version string
stephentoub Aug 19, 2022
868a20c
Removing boxing and char string allocation in GenerateExtendedAttribu…
stephentoub Aug 19, 2022
c83e97c
Fix a couple unnecessary dictionary lookups
stephentoub Aug 19, 2022
82b8909
Replace Enum.HasFlag usage
stephentoub Aug 19, 2022
60145e9
Remove allocations from Write{Posix}Name
stephentoub Aug 19, 2022
e9a32ec
Replace ArrayPool use with string.Create
stephentoub Aug 19, 2022
d632a95
Replace more superfluous ArrayPool usage
stephentoub Aug 19, 2022
fbf22b3
Remove ArrayPool use from System.IO.Compression.ZipFile
stephentoub Aug 20, 2022
60d9352
Fix inverted condition
stephentoub Aug 20, 2022
24aa3a2
Use generic math to parse octal
stephentoub Aug 20, 2022
25b32ff
Remove allocations from StringReader and string.Split
stephentoub Aug 20, 2022
9649015
Remove magic string allocation for Ustar when not V7
stephentoub Aug 20, 2022
c5a8bfe
Remove file name and directory name allocation in GenerateExtendedAtt…
stephentoub Aug 20, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Avoid unnecessary byte[] allocations
  • Loading branch information
stephentoub authored and github-actions committed Aug 21, 2022
commit 8c075247bac31898b2f90b1b1fcbb1d8a85fa564
Original file line number Diff line number Diff line change
Expand Up @@ -518,11 +518,16 @@ private void ReadUstarAttributes(Span<byte> buffer)
// Throws if end of stream is reached or if an attribute is malformed.
private void ReadExtendedAttributesBlock(Stream archiveStream)
{
byte[]? buffer = CreateExtendedAttributesBufferIfSizeIsValid();
if (buffer != null)
if (_size != 0)
{
archiveStream.ReadExactly(buffer);
ReadExtendedAttributesFromBuffer(buffer, _name);
ValidateSize();
byte[] buffer = ArrayPool<byte>.Shared.Rent((int)_size);
Span<byte> span = buffer.AsSpan(0, (int)_size);

archiveStream.ReadExactly(span);
ReadExtendedAttributesFromBuffer(span, _name);

ArrayPool<byte>.Shared.Return(buffer);
}
}

Expand All @@ -531,32 +536,30 @@ private void ReadExtendedAttributesBlock(Stream archiveStream)
private async ValueTask ReadExtendedAttributesBlockAsync(Stream archiveStream, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
byte[]? buffer = CreateExtendedAttributesBufferIfSizeIsValid();
if (buffer != null)

if (_size != 0)
{
await archiveStream.ReadExactlyAsync(buffer, cancellationToken).ConfigureAwait(false);
ReadExtendedAttributesFromBuffer(buffer, _name);
}
}
ValidateSize();
byte[] buffer = ArrayPool<byte>.Shared.Rent((int)_size);
Memory<byte> memory = buffer.AsMemory(0, (int)_size);

// Return a byte array if the size field has a valid value for extended attributes. Otherwise, return null, or throw.
private byte[]? CreateExtendedAttributesBufferIfSizeIsValid()
{
Debug.Assert(_typeFlag is TarEntryType.ExtendedAttributes or TarEntryType.GlobalExtendedAttributes);
await archiveStream.ReadExactlyAsync(memory, cancellationToken).ConfigureAwait(false);
ReadExtendedAttributesFromBuffer(memory.Span, _name);

// It is not expected that the extended attributes data section will be longer than Array.MaxLength, considering
// the size field is 12 bytes long, which fits a number with a value under int.MaxValue.
if (_size > Array.MaxLength)
{
throw new InvalidOperationException(string.Format(SR.TarSizeFieldTooLargeForEntryType, _typeFlag.ToString()));
ArrayPool<byte>.Shared.Return(buffer);
}
}

if (_size == 0)
private void ValidateSize()
{
if ((uint)_size > (uint)Array.MaxLength)
{
return null;
ThrowSizeFieldTooLarge();
}

return new byte[(int)_size];
[DoesNotReturn]
void ThrowSizeFieldTooLarge() =>
throw new InvalidOperationException(string.Format(SR.TarSizeFieldTooLargeForEntryType, _typeFlag.ToString()));
}

// Returns a dictionary containing the extended attributes collected from the provided byte buffer.
Expand All @@ -581,11 +584,16 @@ private void ReadExtendedAttributesFromBuffer(ReadOnlySpan<byte> buffer, string
// Throws if end of stream is reached.
private void ReadGnuLongPathDataBlock(Stream archiveStream)
{
byte[]? buffer = CreateGnuLongDataBufferIfSizeIsValid();
if (buffer != null)
if (_size != 0)
{
archiveStream.ReadExactly(buffer);
ReadGnuLongPathDataFromBuffer(buffer);
ValidateSize();
byte[] buffer = ArrayPool<byte>.Shared.Rent((int)_size);
Span<byte> span = buffer.AsSpan(0, (int)_size);

archiveStream.ReadExactly(span);
ReadGnuLongPathDataFromBuffer(span);

ArrayPool<byte>.Shared.Return(buffer);
}
}

Expand All @@ -595,11 +603,17 @@ private void ReadGnuLongPathDataBlock(Stream archiveStream)
private async ValueTask ReadGnuLongPathDataBlockAsync(Stream archiveStream, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
byte[]? buffer = CreateGnuLongDataBufferIfSizeIsValid();
if (buffer != null)

if (_size != 0)
{
await archiveStream.ReadExactlyAsync(buffer, cancellationToken).ConfigureAwait(false);
ReadGnuLongPathDataFromBuffer(buffer);
ValidateSize();
byte[] buffer = ArrayPool<byte>.Shared.Rent((int)_size);
Memory<byte> memory = buffer.AsMemory(0, (int)_size);

await archiveStream.ReadExactlyAsync(memory, cancellationToken).ConfigureAwait(false);
ReadGnuLongPathDataFromBuffer(memory.Span);

ArrayPool<byte>.Shared.Return(buffer);
}
}

Expand All @@ -618,24 +632,6 @@ private void ReadGnuLongPathDataFromBuffer(ReadOnlySpan<byte> buffer)
}
}

// Return a byte array if the size field has a valid value for GNU long metadata entry data. Otherwise, return null, or throw.
private byte[]? CreateGnuLongDataBufferIfSizeIsValid()
{
Debug.Assert(_typeFlag is TarEntryType.LongLink or TarEntryType.LongPath);

if (_size > Array.MaxLength)
{
throw new InvalidOperationException(string.Format(SR.TarSizeFieldTooLargeForEntryType, _typeFlag.ToString()));
}

if (_size == 0)
{
return null;
}

return new byte[(int)_size];
}

// Tries to collect the next extended attribute from the string wrapped by the specified reader.
// Extended attributes are saved in the ISO/IEC 10646-1:2000 standard UTF-8 encoding format.
// https://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
Expand Down Expand Up @@ -524,8 +525,18 @@ private int WriteGnuFields(Span<byte> buffer)
private static void WriteData(Stream archiveStream, Stream dataStream, long actualLength)
{
dataStream.CopyTo(archiveStream); // The data gets copied from the current position

int paddingAfterData = TarHelpers.CalculatePadding(actualLength);
archiveStream.Write(new byte[paddingAfterData]);
if (paddingAfterData != 0)
{
Debug.Assert(paddingAfterData <= TarHelpers.RecordSize);

Span<byte> padding = stackalloc byte[TarHelpers.RecordSize];
padding = padding.Slice(0, paddingAfterData);
padding.Clear();

archiveStream.Write(padding);
}
}

// Asynchronously writes the current header's data stream into the archive stream.
Expand All @@ -534,8 +545,17 @@ private static async Task WriteDataAsync(Stream archiveStream, Stream dataStream
cancellationToken.ThrowIfCancellationRequested();

await dataStream.CopyToAsync(archiveStream, cancellationToken).ConfigureAwait(false); // The data gets copied from the current position

int paddingAfterData = TarHelpers.CalculatePadding(actualLength);
await archiveStream.WriteAsync(new byte[paddingAfterData], cancellationToken).ConfigureAwait(false);
if (paddingAfterData != 0)
{
byte[] buffer = ArrayPool<byte>.Shared.Rent(paddingAfterData);

Array.Clear(buffer, 0, paddingAfterData);
await archiveStream.WriteAsync(buffer.AsMemory(0, paddingAfterData), cancellationToken).ConfigureAwait(false);

ArrayPool<byte>.Shared.Return(buffer);
}
}

// Dumps into the archive stream an extended attribute entry containing metadata of the entry it precedes.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,9 @@ private async Task WriteEntryAsyncInternal(TarEntry entry, CancellationToken can
// by two records consisting entirely of zero bytes.
private void WriteFinalRecords()
{
byte[] emptyRecord = new byte[TarHelpers.RecordSize];
Span<byte> emptyRecord = stackalloc byte[TarHelpers.RecordSize];
emptyRecord.Clear();

_archiveStream.Write(emptyRecord);
_archiveStream.Write(emptyRecord);
}
Expand All @@ -374,9 +376,14 @@ private void WriteFinalRecords()
// This method is called from DisposeAsync, so we don't want to propagate a cancelled CancellationToken.
private async ValueTask WriteFinalRecordsAsync()
{
byte[] emptyRecord = new byte[TarHelpers.RecordSize];
await _archiveStream.WriteAsync(emptyRecord, cancellationToken: default).ConfigureAwait(false);
await _archiveStream.WriteAsync(emptyRecord, cancellationToken: default).ConfigureAwait(false);
const int TwoRecordSize = TarHelpers.RecordSize * 2;

byte[] twoEmptyRecords = ArrayPool<byte>.Shared.Rent(TwoRecordSize);
Array.Clear(twoEmptyRecords, 0, TwoRecordSize);

await _archiveStream.WriteAsync(twoEmptyRecords.AsMemory(0, TwoRecordSize), cancellationToken: default).ConfigureAwait(false);

ArrayPool<byte>.Shared.Return(twoEmptyRecords);
}

private (string, string) ValidateWriteEntryArguments(string fileName, string? entryName)
Expand Down