Skip to content

Array.ConstrainedCopy doesn't enjoy quick path treatment as Array.Copy #122518

@GeeLaw

Description

@GeeLaw

Description

Recent refactoring of Array.Copy overloads and Array.ConstrainedCopy has unfairly penalized the callers of Array.ConstrainedCopy by not providing a fast path for the latter but providing one for the former.

using System;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

namespace IsConstrainedCopySlower
{
  public class Program
  {
    // Intentionally small to measure the overhead before starting the actual work.
    private readonly int[] src = new int[1];
    private readonly int[] dst = new int[1];

    [Benchmark]
    public void ArrayCopy() { Array.Copy(src, 0, dst, 0, 1); }

    [Benchmark]
    public void ArrayConstrainedCopy() { Array.ConstrainedCopy(src, 0, dst, 0, 1); }

    public static void Main() { BenchmarkRunner.Run<Program>(); }
  }
}
/*

.NET 10, Windows 11 (24H2) x64.

| Method               | Mean      | Error     | StdDev    |
|--------------------- |----------:|----------:|----------:|
| ArrayCopy            | 0.6524 ns | 0.0042 ns | 0.0040 ns |
| ArrayConstrainedCopy | 3.5042 ns | 0.0012 ns | 0.0011 ns |

.NET 9, Windows 11 (24H2) x64.

| Method               | Mean     | Error     | StdDev    |
|--------------------- |---------:|----------:|----------:|
| ArrayCopy            | 3.749 ns | 0.0030 ns | 0.0027 ns |
| ArrayConstrainedCopy | 5.371 ns | 0.1229 ns | 0.2750 ns |

.NET Framework 4.8.1, Windows 11 (24H2) x64.

| Method               | Mean     | Error     | StdDev    |
|--------------------- |---------:|----------:|----------:|
| ArrayCopy            | 6.879 ns | 0.1550 ns | 0.3592 ns |
| ArrayConstrainedCopy | 6.963 ns | 0.1554 ns | 0.3928 ns |

*/

Notice how in .NET Framework (and earlier versions of .NET I think, but I didn't have the patience to run them), the overheads for Array.Copy and Array.ConstrainedCopy are the same (the difference is explained by fluctuations), but in .NET 9/10, the overhead of Array.ConstrainedCopy is much higher than that of Array.Copy with high confidence.

Analysis

I discovered this change when I was casually browsing the code base, and was wondering whether under=optimization would hurt callers of ConstrainedCopy, and verified.

In Array.cs:

  • Line 399 is the overload void Copy(Array, int, Array, int, int), which includes a fast path (big if) and calls the worker (essentially, this is the "meat" of Array.ConstrainedCopy for single-dimensional zero-based arrays.
  • Line 360 is void ConstrainedCopy(Array, int, Array, int, int), which doesn't include a fast path and just calls CopyImpl. The latter does complex error checking before reaching line 459, which is the "meat" of ConstrainedCopy (but it also handles multi-dimensional arrays).

The suggested solution is to add a fast path in ConstrainedCopy for single-dimensional zero-based arrays like Copy.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions