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

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions