Fast array copy in C#
Asked Answered
A

3

14

I have a C# class that contains an int[] array (and a couple of other fields, but the array is the main thing). The code often creates copies of this class and profiling shows that the Array.Copy() call to copy this array takes a lot of time. What can I do to make it faster?

The array size is very small and constant: 12 elements. So ideally I'd like something like a C-style array: a single block of memory that's inside the class itself (not a pointer). Is this possible in C#? (I can use unsafe code if needed.)

I've already tried:

1) Using a UIn64 and bit-shifting instead of the array. (The values of each element are also very small.) This does make the copy fast, but slows down the program overall.

2) Using separate fields for each array element: int element0, int element1, int element2, etc. Again, this is slower overall when I have to access the element at a given index.

Assistance answered 23/4, 2014 at 15:27 Comment(2)
A little more context (and code maybe) might help this question. As @mason said, it's hard to believe copying 12 elements is a bottle neck, but I'm assuming you are doing this frequently, maybe in a loop. So the context in which you are doing it might bring some better answers.Saboteur
Yes, it is being called frequently, in a loop.Assistance
H
22

I would checkout the System.Buffer.BlockCopy if you are really concerned about speed.

http://msdn.microsoft.com/en-us/library/system.buffer.blockcopy.aspx

Simple Example:

  int[] a = new int[] {1,2,3,4,5,6,7,8};
  int[] b = new int[a.Length];
  int size = sizeof(int);
  int length = a.Length * size;               
  System.Buffer.BlockCopy(a, 0, b, 0, length);

Great discussion on it over here: Array.Copy vs Buffer.BlockCopy

Heavenly answered 23/4, 2014 at 15:31 Comment(1)
Thanks for Buffer.BlockCopy - that speeds it up by ~10% (overall, not just the copy), so it's definitely a good improvement for the effort involved. :)Assistance
N
6

This post is old, but anyone in a similar situation as the OP should have a look at fixed size buffers in structs. They are exactly what OP was asking for: an array of primitive types with a constant size stored directly in the class.

You can create a struct to represent your collection, which will contain the fixed size buffer. The data will be stored directly within the struct, which will be stored directly within your class. You can copy through simple assignment.

They come with a few caveats:

  • They can only be used with primitive types.
  • They require the "unsafe" keyword on your struct.
  • Size must be known at compile time.

It used to be that you had to use the fixed keyword and pointers to access them, but recent changes to C# catering to performance programming have made that unnecessary. You can now work with them just like arrays.

public unsafe struct MyIntContainer
{
    private fixed int myIntegers[12];

    public int this[int index]
    {
        get => this.myIntegers[index];
        set => this.myIntegers[index] = value;
    }
}

There is no built-in bound checking, so it would be best for you to include that yourself on such a property, encapsulating any functionality which skips bound checks inside of a method. I am on mobile, or I would have worked that into my example.

Niel answered 21/5, 2019 at 5:22 Comment(3)
I get a "Type expected" compiler error on the "this" in public this[int index], targeting .NET 4.7.2Hornswoggle
but commenting that function out, the copying of structs is 80x faster for a byte array size of 1024 than elementwise copyingHornswoggle
@Hornswoggle Thanks Markus. I have corrected the example code. What a silly bug!Niel
D
3

You asked about managed arrays. If you are content to use fixed / unsafe, this can be very fast.

struct is assignable, like any primitive. Almost certainly faster than Buffer.BlockCopy() or any other method, due to the lack of method call overhead:

public unsafe struct MyStruct //the actual struct used, contains all
{
    public int a;
    public unsafe fixed byte buffer[16];
    public ulong b;
    //etc.
}

public unsafe struct FixedSizeBufferWrapper //contains _only_ the buffer
{
    public unsafe fixed byte buffer[16];
}

unsafe 
{
    fixed (byte* bufferA = myStructA.buffer, bufferB = myStructB.buffer)
    {
        *((FixedSizeBufferWrapper*)bufferA) =
        *((FixedSizeBufferWrapper*)bufferB);
    }
}

We cast fixed-size byte buffers from each of your original structs to the wrapper pointer type and dereference each pointer SO THAT we can assign one to the other by value; assigning fixed buffers directly is not possible, hence the wrapper, which is basically zero overhead (it just affects values used in pointer arithmetic that is done anyway). That wrapper is only ever used for casting.

We have to cast because (at least in my version of C#) we cannot assign anything other than a primitive type (usually byte[]) as the buffer, and we aren't allowed to cast inside fixed(...).

EDIT: This appears get translated into a call to Buffer.Memcpy() (specifically Buffer.memcpy4() in my case, in Unity / Mono) under the hood to do the copy.

Detrimental answered 2/12, 2021 at 16:54 Comment(1)
Clever! I'm of course not working on that code anymore, but I still appreciate you posting an answer to my 7 year-old question. Tempted to try this out just to see how much faster it is.Assistance

© 2022 - 2024 — McMap. All rights reserved.