C# High-Performance Array Copy Experiment
Preface Yesterday wc(Wyu_Cnk) asked a question Is there a more elegant way to copy multidimensional arrays in C#? Didn't they ask the right person? I've been working on image processing recently and dealing with memory, so I immediately thought of doing memory copies in C# just like in C/C++. Elegant? no, what we want is to show off, and the performance has to be strong too 🕶 Concepts First, let's clarify A Multi-dimensional Array in C# looks like this byte[,] arr = new by
目录10 个章节
Preface
Yesterday wc(Wyu_Cnk) asked a question
Is there a more elegant way to copy multidimensional arrays in C#?
Didn't they ask the right person? I've been working on image processing recently and dealing with memory, so I immediately thought of doing memory copies in C# just like in C/C++.
Elegant? no, what we want is to show off, and the performance has to be strong too 🕶
Concepts
First, let's clarify
A Multi-dimensional Array in C# looks like this
byte[,] arr = new byte[10, 10];
The following syntax is a Jagged Array, which is an array of arrays
byte[][] arr = new byte[10][];
For the specific differences, please see the references at the end of this article~
Getting Started
Next I'll introduce several methods for copying arrays, then compare the performance of the different implementations
Let's define some constants: SIZE is the array size, and COUNT is the number of loop iterations we'll use for the copy tests
const int COUNT = 32, SIZE = 32 << 20;
A bit-shift is used here: left-shifting 32 by 20 bits appends 20 zeros to the binary representation of 32, which is equivalent to 32*2^20. It's just a way to define a fairly large number. Computers nowadays are so powerful that copying a smaller array would be too fast to see any difference.
Next, define several arrays. Here I've written five pairs of one-dimensional arrays. Each different array-copy method is tested with a different array, so as to avoid CPU caching.
private 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],
dSource = new byte[SIZE],
dTarget = new byte[SIZE],
eSource = new byte[SIZE],
eTarget = new byte[SIZE];
Then we'll test all of these array-copy methods
- Clone approach:
array.Clone() - Linq:
array.Select(x=>x).ToArray() Array.Copy()Buffer.BlockCopy()Buffer.MemoryCopy()
Clone Approach
In C#, any object that implements the ICloneable interface has a Clone method
So arrays can also be copied this way
It's simple, just var newArray = (byte[])array.Clone()
The code is as follows
static void TestArrayClone() {
var sw = Stopwatch.StartNew();
sw.Start();
for (var i = 0; i < COUNT; i++) {
dTarget = (byte[])dSource.Clone();
}
sw.Stop();
Console.WriteLine("Array.Clone: {0:N0} ticks, {1} ms", sw.ElapsedTicks, sw.ElapsedMilliseconds);
}
Stopwatch is used here to record the execution time. The other copy methods below also have this. We'll use the ticks and milliseconds computed this way to compare the performance differences among the implementations.
Linq Approach
You don't even need to test it to know this approach is the slowest
It just iterates through the elements one by one, then reconstructs a new array
The code is as follows
eTarget = eSource.Select(x => x).ToArray();
Array.Copy()
Use the static method Array.Copy() to copy arrays
Tip: Performance is pretty good, and it's convenient to use
The code is as follows; you only need to specify the length
Array.Copy(cSource, cTarget, SIZE);
Or use another overload that lets you specify the offset of each of the two arrays separately
Array.Copy(cSource, 0, cTarget, 0, SIZE);
Buffer.BlockCopy()
The Buffer class is used to manipulate arrays of primitive types
Manipulates arrays of primitive types.
The code is as follows
Buffer.BlockCopy(bSource, 0, bTarget, 0, SIZE);
Same as the second overload of Array.Copy above, you need to specify the offset of each of the two arrays separately
Buffer.MemoryCopy()
This is an unsafe method that requires pointers 😀 In theory it has the best performance
This is my favorite method~~(high-class)~~
To use unsafe code, first enable the allow unsafe code option in the compiler settings.
The function signature of this MemoryCopy method is as follows
static unsafe void MemoryCopy(void* source, void* destination, long destinationSizeInBytes, long sourceBytesToCopy)
The first two parameters are pointer types, and the latter two are lengths. Note that these are byte counts, not the number of array elements
In C#, a byte occupies 8 bits, which is exactly one byte, so you can just use the element count. If it's an array of another type, you have to calculate the byte count based on the type's size, then pass that in.
The code is as follows. Add the unsafe keyword to the function definition in order to use fixed blocks and pointers
static unsafe void TestBufferMemoryCopy() {
var sw = Stopwatch.StartNew();
fixed (byte* pSrc = fSource, pDest = fTarget) {
for (int i = 0; i < COUNT; i++) {
Buffer.MemoryCopy(pSrc, pDest, SIZE, SIZE);
}
}
Console.WriteLine("Buffer.MemoryCopy (2d): {0:N0} ticks, {1} ms", sw.ElapsedTicks, sw.ElapsedMilliseconds);
}
Then
While searching for materials, I also found that some people used the Buffer.Memcpy method, but this is an internal method that isn't publicly exposed, so you have to use some black magic to call it
I tinkered with it for a long time and finally came up with the code to call this non-public method
unsafe delegate void Memcpy(byte* src, byte* dest, int len);
internal class Program {
private static Memcpy memcpy;
static Program() {
var methodInfo = typeof(Buffer).GetMethod(
"Memcpy",
BindingFlags.Static | BindingFlags.NonPublic,
null,
new Type[] { typeof(byte*), typeof(byte*), typeof(int) },
null
);
if (methodInfo == null) {
Console.WriteLine("init failed! method is not found.");
return;
}
memcpy = (Memcpy)Delegate.CreateDelegate(typeof(Memcpy), methodInfo);
}
}
In actual tests, the performance of this Memcpy and MemoryCopy is about the same
I took a look at the .NetCore source code
Sure enough, the two implementations are basically the same
// Used by ilmarshalers.cpp
internal static unsafe void Memcpy(byte* dest, byte* src, int len)
{
Debug.Assert(len >= 0, "Negative length in memcpy!");
Memmove(ref *dest, ref *src, (nuint)(uint)len /* force zero-extension */);
}
The other one
public static unsafe void MemoryCopy(void* source, void* destination, long destinationSizeInBytes, long sourceBytesToCopy)
{
if (sourceBytesToCopy > destinationSizeInBytes) { ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.sourceBytesToCopy);
}
Memmove(ref *(byte*)destination, ref *(byte*)source, checked((nuint)sourceBytesToCopy));
}
Both of these ultimately call the Memmove method
The difference is just that the parameters of these two methods are different.
benchmark
Performance test results
Array.Copy: 49,923,612 ticks, 49 ms
Buffer.BlockCopy: 52,497,377 ticks, 52 ms
Buffer.Memcpy: 49,067,555 ticks, 49 ms
Buffer.MemoryCopy (2d): 48,982,014 ticks, 48 ms
Array.Clone: 360,640,218 ticks, 360 ms
Linq: 1,988,890,052 ticks, 1988 ms
Array.Copy: 48,653,699 ticks, 48 ms
Buffer.BlockCopy: 48,040,093 ticks, 48 ms
Buffer.Memcpy: 47,818,057 ticks, 47 ms
Buffer.MemoryCopy (2d): 49,084,413 ticks, 49 ms
Array.Clone: 406,848,666 ticks, 406 ms
Linq: 1,943,498,307 ticks, 1943 ms
Array.Copy: 48,943,429 ticks, 48 ms
Buffer.BlockCopy: 47,989,824 ticks, 47 ms
Buffer.Memcpy: 48,053,817 ticks, 48 ms
Buffer.MemoryCopy (2d): 49,065,368 ticks, 49 ms
Array.Clone: 364,339,126 ticks, 364 ms
Linq: 1,999,189,800 ticks, 1999 ms
Array.Copy: 49,679,913 ticks, 49 ms
Buffer.BlockCopy: 48,651,877 ticks, 48 ms
Buffer.Memcpy: 48,262,443 ticks, 48 ms
Buffer.MemoryCopy (2d): 49,683,361 ticks, 49 ms
Array.Clone: 429,384,291 ticks, 429 ms
Linq: 1,932,109,712 ticks, 1932 ms
Which method you should use to copy arrays is obvious at a glance now, right~ 😃
References
- Multi-dimensional and Jagged Arrays - https://www.pluralsight.com/guides/multidimensional-arrays-csharp
- Any faster way of copying arrays in C#? - https://stackoverflow.com/questions/5099604/any-faster-way-of-copying-arrays-in-c
_EOF_
C# High-Performance Array Copy Experiment
- 本文作者:
- 程序设计实验室
- 版权声明:
- 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
相关文章
开发现代化的.NetCore控制台程序:(4)使用GithubAction自动构建以及发布nuget包
GitHub Action自动构建发布nuget包,省时省力。Secrets管理token,workflow一劳永逸。吐槽nuget推送重复输入token,烦人!
2023-11-13 · C# · 约 1 分钟
Spark.NET:一个试图把 Django / Rails 式开发体验带回 .NET 世界的全栈 Web 框架。
又一个把Django/Rails那套“约定优于配置”搬进.NET生态的尝试,看着有点意思。预置了认证、队列、定时任务这些破事,省得自己天天重复造轮子。但767个star连个正式release都没有,就敢打着“生产级”旗号?呵,C#全栈爱好者们可以先玩玩,但别指望它能替代ASP.NET Core的体量。
2026-04-29 · C# · 约 1 分钟
C#学习(一)委托的概念和使用
前言 C#学习系列是根据我以前的笔记整理出来复习,顺便发一下文章做个记录。 先引用一个介绍: C# 中的委托(Delegate)类似于 C 或 C++ 中函数的指针。委托(Delegate) 是存有对某个方法的引用的一种引用类型变量。引用可在运行时被改变。 委托(Delegate)特别用于实现事件和回调方法。所有的委托(Delegate)都派生自 System.Delegate 类。 根据使用其他
2021-12-18 · C# · 约 1 分钟
C#学习(三)深入理解委托、匿名方法和-Lambda-表达式
前言 lambda的概念建立在委托的基础上,委托,实现了类型安全的回调方法。在.NET 中回调无处不在,所以委托也无处不在,事件模型建立在委托机制上,Lambda 表达式本质上就是一种匿名委托。本文中将完成一次关于委托的旅行,全面阐述委托及其核心话题,逐一梳理委托、委托链、事件、匿名方法和 Lambda 表达式。 委托的定义 了解委托,从其定义开始,通常一个委托被声明为: public deleg
2021-12-18 · C# · 约 1 分钟
讨论
共 0 条加载评论中…