Skip to content

Comments

Replace .Clone() with explicit array allocation and copy in Arrays#662

Open
paulomorgado wants to merge 1 commit intobcgit:masterfrom
paulomorgado:performance/arrays
Open

Replace .Clone() with explicit array allocation and copy in Arrays#662
paulomorgado wants to merge 1 commit intobcgit:masterfrom
paulomorgado:performance/arrays

Conversation

@paulomorgado
Copy link

Replace .Clone() with explicit array allocation and copy in Arrays

Summary

Refactors all array cloning methods in crypto/src/util/Arrays.cs to replace the (T[])array.Clone() pattern with explicit allocation and copy. This gives the runtime more precise information about the operation, avoids unnecessary zero-initialization overhead on .NET 5.0+, and uses the most efficient copy path for each element type.

Describe your changes

  • Clone(T[] data) overloads (bool, byte, short, ushort, int, uint, long, ulong): replaced (T[])data.Clone() with GC.AllocateUninitializedArray<T> (on .NET 5.0+, falling back to new T[] on older targets) followed by Buffer.BlockCopy, which operates on raw bytes and is highly optimized for blittable/primitive types.
  • InternalCopyBuffer<T>: replaced (T[])buf.Clone() with GC.AllocateUninitializedArray<T> + Array.Copy, since the generic constraint doesn't guarantee a blittable type suited for Buffer.BlockCopy.

Benchmarks

[MemoryDiagnoser]
[SimpleJob(RuntimeMoniker.Net10_0)]
[SimpleJob(RuntimeMoniker.Net481)]
[HideColumns(Column.Error, Column.StdDev, Column.Median, Column.Job)]
public class CloneImplementationComparison
{
    [Params(16, 128, 1024)]
    public int ArraySize;

    private byte[] sourceArray;

    [GlobalSetup]
    public void Setup()
    {
        sourceArray = new byte[ArraySize];
        for (int i = 0; i < ArraySize; i++)
            sourceArray[i] = (byte)(i & 0xFF);
    }

    [Benchmark(Baseline = true)]
    public byte[] ArrayClone()
    {
        return (byte[])sourceArray.Clone();
    }

    [Benchmark]
    public byte[] NewArrayBlockCopy()
    {
#if NET5_0_OR_GREATER
        byte[] temp = GC.AllocateUninitializedArray<byte>(sourceArray.Length);
#else
        byte[] temp = new byte[sourceArray.Length];
#endif
        Buffer.BlockCopy(sourceArray, 0, temp, 0, sourceArray.Length);
        return temp;
    }
}

Benchmark results

Benchmarked on: Windows 11, 13th Gen Intel Core i9-13900K, .NET 10.0.3 / .NET Framework 4.8.1.

BenchmarkDotNet v0.15.8, Windows 11 (10.0.26200.7918/25H2/2025Update/HudsonValley2)
13th Gen Intel Core i9-13900K 3.00GHz, 1 CPU, 32 logical and 24 physical cores
.NET SDK 10.0.200-preview.0.26103.119
  [Host]               : .NET 10.0.3 (10.0.3, 10.0.326.7603), X64 RyuJIT x86-64-v3
  .NET 10.0            : .NET 10.0.3 (10.0.3, 10.0.326.7603), X64 RyuJIT x86-64-v3
  .NET Framework 4.8.1 : .NET Framework 4.8.1 (4.8.9325.0), X64 RyuJIT VectorSize=256
Method Runtime ArraySize Mean Ratio Allocated
ArrayClone .NET 10.0 16 33.286 ns 1.00 40 B
NewArrayBlockCopy .NET 10.0 16 4.787 ns 0.14 40 B
ArrayClone .NET Framework 4.8.1 16 18.655 ns 1.00 40 B
NewArrayBlockCopy .NET Framework 4.8.1 16 7.217 ns 0.39 40 B
ArrayClone 128 33.653 ns 1.00 152 B
NewArrayBlockCopy .NET 10.0 128 7.731 ns 0.23 152 B
ArrayClone .NET Framework 4.8.1 128 22.591 ns 1.00 152 B
NewArrayBlockCopy .NET Framework 4.8.1 128 11.276 ns 0.50 152 B
ArrayClone .NET 10.0 1024 50.312 ns 1.00 1048 B
NewArrayBlockCopy .NET 10.0 1024 33.917 ns 0.67 1048 B
ArrayClone .NET Framework 4.8.1 1024 44.428 ns 1.00 1051 B
NewArrayBlockCopy .NET Framework 4.8.1 1024 34.993 ns 0.79 1051 B

The new approach is 7× faster for small arrays (16 elements) on .NET 10.0, and consistently 20–50% faster across all sizes on .NET Framework 4.8.1. Memory allocation is identical. No behavioral change — null inputs continue to return null.

How has this been tested?

Using the existing test.

Checklist before requesting a review

  • I have performed a self-review of my code
  • I have kept the patch limited to only change the parts related to the patch
  • This change requires a documentation update

See also Contributing Guidelines.

Refactored array cloning methods to avoid .Clone() in favor of explicit allocation and copying. Uses GC.AllocateUninitializedArray<T> on .NET 5.0+ for performance, falling back to new T[] otherwise. Buffer.BlockCopy is used for primitive types, and Array.Copy for generic buffers. This change improves control over array cloning and may reduce unnecessary zero-initialization overhead.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant