Question

I made some tests that used Array.Copy to copy parts of an array to another.

The first test used Array.Copy on an array of value-types

struct ValueApple {
    public int Redness;
}

ValueApple[] a1 = ...
ValueApple[] a2 = ...

Array.Copy(a1, a2, a1.Length);

Then the second test used Array.Copy on an array of reference-types

class ReferenceApple {
    public int Redness;
}

ReferenceApple[] a1 = ...
ReferenceApple[] a2 = ...

Array.Copy(a1, a2, a1.Length);

I came with results that showed that copying between arrays of value-types is faster than copying between arrays of reference types.

So my question is:

Is that true ? Is copying from an array of value-types faster than from an array of reference-types and why ?


Then my second question would be:

If copying from an array of value-types is faster. If I create a value-type wrapper for a reference-type, won't that help me boost performace ?

For example

struct ValueWrapper<T> {
    public T Value;
}

class ReferenceApple { }

ValueWrapper<ReferenceApple>[] a1 = new ValueWrapper<ReferenceApple>[20];
ValueWrapper<ReferenceApple>[] a2 = new ValueWrapper<ReferenceApple>[40];

Array.Copy(a1, a2, 20);

Should this also be faster than using a plain ReferenceApple[] array ?

Was it helpful?

Solution

For ValueApple[] vs ReferenceApple[]; the particular difference depends a lot on target platform; on x86 it does seem to have a bit of overhead in the reference-type array, but that is much less noticeable on x64 (which is faster than either x86 version, despite the fact that the ReferenceApple[] version is now copying twice as much data):

x86

ValueApple: 3295ms
ValueApple CopyTo: 3283ms
ReferenceApple: 4345ms
ReferenceApple CopyTo: 4234ms

x64

ValueApple: 1819ms
ValueApple CopyTo: 1864ms
ReferenceApple: 2251ms
ReferenceApple CopyTo: 2335ms

I would not say that is worth bending code for. You also need to ask: "is array copy my genuine bottleneck?". If it isn't, this test is entirely unhelpful. So first: find what your bottleneck is.

There are some cases where the array-of-struct trick can be helpful, but usually that is more related to very edge-case high-memory / garbage-collection scenarios involving long-lived huge data. Not regular code with arrays of 20/40 items.

Above numbers based on:

using System;
using System.Diagnostics;
struct ValueApple
{
    public int Redness;
}
class ReferenceApple
{
    public int Redness;
}

static class Program {
    static void Main()
    {
        const int LOOP = 50000000;

        ValueApple[] a1 = new ValueApple[20];
        ValueApple[] a2 = new ValueApple[40];
        var watch = Stopwatch.StartNew();
        for (int i = 0; i < LOOP; i++)
        {
            Array.Copy(a1, a2, 20);
        }
        watch.Stop();
        Console.WriteLine("ValueApple: {0}ms", watch.ElapsedMilliseconds);

        watch = Stopwatch.StartNew();
        for (int i = 0; i < LOOP; i++)
        {
            a1.CopyTo(a2, 0);
        }
        watch.Stop();
        Console.WriteLine("ValueApple CopyTo: {0}ms", watch.ElapsedMilliseconds);

        ReferenceApple[] a3 = new ReferenceApple[20];
        ReferenceApple[] a4 = new ReferenceApple[40];
        watch = Stopwatch.StartNew();
        for (int i = 0; i < LOOP; i++)
        {
            Array.Copy(a3, a4, 20);
        }
        watch.Stop();
        Console.WriteLine("ReferenceApple: {0}ms", watch.ElapsedMilliseconds);

        watch = Stopwatch.StartNew();
        for (int i = 0; i < LOOP; i++)
        {
            a3.CopyTo(a4, 0);
        }
        watch.Stop();
        Console.WriteLine("ReferenceApple CopyTo: {0}ms", watch.ElapsedMilliseconds);

        Console.WriteLine("(done)");
        Console.ReadKey();
    }
}

For wrapped vs not:

I call your results into question; my numbers:

Wrapper<T>: 2175ms
Direct: 2231ms
Wrapper: 2165ms

(the last is a non-generic version of the same)

Which is basically "about the same, give or take random CPU activity". A few ms over 50M iterations is certainly nothing worth bending code out of shape over...

Importantly, make sure any such test is in release mode, and executed outside of the debugger.

My test code:

using System;
using System.Diagnostics;
struct ValueWrapper<T> {
    public T Value;
}
struct ValueWrapper
{
    public Apple Value;
}
class Apple { }

static class Program {
    static void Main()
    {
        const int LOOP = 50000000;

        ValueWrapper<Apple>[] a1 = new ValueWrapper<Apple>[20];
        ValueWrapper<Apple>[] a2 = new ValueWrapper<Apple>[40];
        var watch = Stopwatch.StartNew();
        for (int i = 0; i < LOOP; i++)
        {
            Array.Copy(a1, a2, 20);
        }
        watch.Stop();
        Console.WriteLine("Wrapper<T>: {0}ms", watch.ElapsedMilliseconds);

        Apple[] a3 = new Apple[20];
        Apple[] a4 = new Apple[40];
        watch = Stopwatch.StartNew();
        for (int i = 0; i < LOOP; i++)
        {
            Array.Copy(a3, a4, 20);
        }
        watch.Stop();
        Console.WriteLine("Direct: {0}ms", watch.ElapsedMilliseconds);

        ValueWrapper[] a5 = new ValueWrapper[20];
        ValueWrapper[] a6 = new ValueWrapper[40];
        watch = Stopwatch.StartNew();
        for (int i = 0; i < LOOP; i++)
        {
            Array.Copy(a5, a6, 20);
        }
        watch.Stop();
        Console.WriteLine("Wrapper: {0}ms", watch.ElapsedMilliseconds);

        Console.WriteLine("(done)");
        Console.ReadKey();
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top