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 许可协议。转载请注明出处!
相关文章
一次小而美的重构:使用 C# 在 Avalonia 中生成真正好看的词云
重构StarBlogPublisher中词云展示功能的过程。作者对比了两个流行的C#词云库,最终选择了功能更强大的Sdcb.WordCloud。文章详细说明了如何使用该库获取词频数据并生成词云图像,并展示了重构前后的效果对比。
2025-04-26 · C# · 约 1 分钟
SharpIco:用纯C#打造零依赖的.ico图标生成器,支持.NET9与AOT编译
SharpIco 是一款纯 C# 实现的跨平台图标生成工具,支持 AOT 编译,无需外部依赖。它能将 PNG 图片分解为多尺寸(16x16至512x512)合成 ICO 图标,并支持图标结构分析。工具提供命令行界面,可通过 dotnet tool 一键安装,并已发布至 NuGet 和 Scoop。项目旨在简化图标生成与验证,追求轻量、纯粹、易集成的使用体验。
2025-05-26 · C# · 约 1 分钟
C#学习(一)委托的概念和使用
前言 C#学习系列是根据我以前的笔记整理出来复习,顺便发一下文章做个记录。 先引用一个介绍: C# 中的委托(Delegate)类似于 C 或 C++ 中函数的指针。委托(Delegate) 是存有对某个方法的引用的一种引用类型变量。引用可在运行时被改变。 委托(Delegate)特别用于实现事件和回调方法。所有的委托(Delegate)都派生自 System.Delegate 类。 根据使用其他
2021-12-18 · C# · 约 1 分钟
C#实现解析Markdown文档并处理图片链接
前言 又是好久没写博客了 其实也不是没写,是最近在「做一个博客」,从2月21日开始,大概一个多星期的时间,疯狂刷进度,边写代码边写了一整系列的博客开发笔记,目前为止已经写了16篇了,然后上3月之后工作有点忙~~加上有点沉迷原神~~,然后进度就暂时慢下来了 事实证明做什么事情一旦停下来就会产生惰性,于是停了这么久,我终于下定决心继续更新! 这次开发的博客用的是C#语言,搭配.netcore技术栈;前
2022-03-19 · C# · 约 1 分钟
讨论
共 0 条加载评论中…