Any faster way of copying arrays in C#?

Use Buffer.BlockCopy. Its entire purpose is to perform fast (see Buffer):

This class provides better performance for manipulating primitive types than similar methods in the System.Array class.

Admittedly, I haven’t done any benchmarks, but that’s the documentation. It also works on multidimensional arrays; just make sure that you’re always specifying how many bytes to copy, not how many elements, and also that you’re working on a primitive array.

Also, I have not tested this, but you might be able to squeeze a bit more performance out of the system if you bind a delegate to System.Buffer.memcpyimpl and call that directly. The signature is:

internal static unsafe void memcpyimpl(byte* src, byte* dest, int len)

It does require pointers, but I believe it’s optimized for the highest speed possible, and so I don’t think there’s any way to get faster than that, even if you had assembly at hand.


Update:

Due to requests (and to satisfy my curiosity), I tested this:

using System;
using System.Diagnostics;
using System.Reflection;

unsafe delegate void MemCpyImpl(byte* src, byte* dest, int len);

static class Temp
{
    //There really should be a generic CreateDelegate<T>() method... -___-
    static MemCpyImpl memcpyimpl = (MemCpyImpl)Delegate.CreateDelegate(
        typeof(MemCpyImpl), typeof(Buffer).GetMethod("memcpyimpl",
            BindingFlags.Static | BindingFlags.NonPublic));
    const int COUNT = 32, SIZE = 32 << 20;

    //Use different buffers to help avoid CPU cache effects
    static byte[]
        aSource = new byte[SIZE], aTarget = new byte[SIZE],
        bSource = new byte[SIZE], bTarget = new byte[SIZE],
        cSource = new byte[SIZE], cTarget = new byte[SIZE];


    static unsafe void TestUnsafe()
    {
        Stopwatch sw = Stopwatch.StartNew();
        fixed (byte* pSrc = aSource)
        fixed (byte* pDest = aTarget)
            for (int i = 0; i < COUNT; i++)
                memcpyimpl(pSrc, pDest, SIZE);
        sw.Stop();
        Console.WriteLine("Buffer.memcpyimpl: {0:N0} ticks", sw.ElapsedTicks);
    }

    static void TestBlockCopy()
    {
        Stopwatch sw = Stopwatch.StartNew();
        sw.Start();
        for (int i = 0; i < COUNT; i++)
            Buffer.BlockCopy(bSource, 0, bTarget, 0, SIZE);
        sw.Stop();
        Console.WriteLine("Buffer.BlockCopy: {0:N0} ticks",
            sw.ElapsedTicks);
    }

    static void TestArrayCopy()
    {
        Stopwatch sw = Stopwatch.StartNew();
        sw.Start();
        for (int i = 0; i < COUNT; i++)
            Array.Copy(cSource, 0, cTarget, 0, SIZE);
        sw.Stop();
        Console.WriteLine("Array.Copy: {0:N0} ticks", sw.ElapsedTicks);
    }

    static void Main(string[] args)
    {
        for (int i = 0; i < 10; i++)
        {
            TestArrayCopy();
            TestBlockCopy();
            TestUnsafe();
            Console.WriteLine();
        }
    }
}

The results:

Buffer.BlockCopy: 469,151 ticks
Array.Copy: 469,972 ticks
Buffer.memcpyimpl: 496,541 ticks

Buffer.BlockCopy: 421,011 ticks
Array.Copy: 430,694 ticks
Buffer.memcpyimpl: 410,933 ticks

Buffer.BlockCopy: 425,112 ticks
Array.Copy: 420,839 ticks
Buffer.memcpyimpl: 411,520 ticks

Buffer.BlockCopy: 424,329 ticks
Array.Copy: 420,288 ticks
Buffer.memcpyimpl: 405,598 ticks

Buffer.BlockCopy: 422,410 ticks
Array.Copy: 427,826 ticks
Buffer.memcpyimpl: 414,394 ticks

Now change the order:

Array.Copy: 419,750 ticks
Buffer.memcpyimpl: 408,919 ticks
Buffer.BlockCopy: 419,774 ticks

Array.Copy: 430,529 ticks
Buffer.memcpyimpl: 412,148 ticks
Buffer.BlockCopy: 424,900 ticks

Array.Copy: 424,706 ticks
Buffer.memcpyimpl: 427,861 ticks
Buffer.BlockCopy: 421,929 ticks

Array.Copy: 420,556 ticks
Buffer.memcpyimpl: 421,541 ticks
Buffer.BlockCopy: 436,430 ticks

Array.Copy: 435,297 ticks
Buffer.memcpyimpl: 432,505 ticks
Buffer.BlockCopy: 441,493 ticks

Now change the order again:

Buffer.memcpyimpl: 430,874 ticks
Buffer.BlockCopy: 429,730 ticks
Array.Copy: 432,746 ticks

Buffer.memcpyimpl: 415,943 ticks
Buffer.BlockCopy: 423,809 ticks
Array.Copy: 428,703 ticks

Buffer.memcpyimpl: 421,270 ticks
Buffer.BlockCopy: 428,262 ticks
Array.Copy: 434,940 ticks

Buffer.memcpyimpl: 423,506 ticks
Buffer.BlockCopy: 427,220 ticks
Array.Copy: 431,606 ticks

Buffer.memcpyimpl: 422,900 ticks
Buffer.BlockCopy: 439,280 ticks
Array.Copy: 432,649 ticks

or, in other words: they’re very competitive; as a general rule, memcpyimpl is fastest, but it’s not necessarily worth worrying about.

Leave a Comment