前言

昨天 wc(Wyu_Cnk) 提了个问题

C# 里多维数组拷贝有没有什么比较优雅的写法?

这不是问对人了吗?正好我最近在搞图像处理,要和内存打交道,我一下就想到了在C#里面直接像C/C++一样做内存拷贝。

优雅?no,要的就是装逼,而且性能还要强

概念

首先澄清一下

C# 里的多维数组 (Multi-dimensional Array) 是这样的

byte[,] arr = new byte[10, 10];

下面这种写法是交错数组 (Jagged Array),就是数组里面套着数组

byte[][] arr = new byte[10][];

具体区别请看文末的参考资料~

开始

接下来介绍几种拷贝数组的方法,然后再比较一下不同实现的性能

定义一下常量,SIZE 表示数组大小,COUNT 表示等会要做拷贝测试的循环次数

const int COUNT = 32, SIZE = 32 << 20;

这里用了移位操作,32左移20位就是在32的二进制数后面补20个0,相当于 32*2^20,只是用来定义一个比较大的数,现在的电脑性能太强了,小一点的数组复制起来太快了,看不出区别。

接着定义几个数组,这里写了五组一维数组,每个不同的数组拷贝方法测试用不同的数组,这样可以避免CPU缓存。

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];

然后把这几个数组拷贝方法都测试一下

  • Clone方式: array.Clone()
  • Linq: array.Select(x=>x).ToArray()
  • Array.Copy()
  • Buffer.BlockCopy()
  • Buffer.MemoryCopy()

Clone 方式

在C#中,只要实现了 ICloneable 接口的对象,就有 Clone 方法

所以数组也可以通过这种方式来实现拷贝

很简单,直接 var newArray = (byte[])array.Clone() 就行了

代码如下

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 来记录执行时间,后面的其他拷贝方法里面也有,等会用这个计算出来的 ticks 和毫秒,可以比较不同实现的性能差距。

Linq方式

其实不用测试也知道这个方式是最慢的

就是一个个元素遍历,再重新构造个新的数组

代码如下

eTarget = eSource.Select(x => x).ToArray();

Array.Copy()

使用静态方法 Array.Copy() 来实现数组复制

提示:性能是不错的,使用也方便

代码如下,只需要指定长度即可

Array.Copy(cSource, cTarget, SIZE);

或者用另一个重载,可以分别指定两个数组的偏移值

Array.Copy(cSource, 0, cTarget, 0, SIZE);

Buffer.BlockCopy()

Buffer 类是用来操作基本类型数组的

Manipulates arrays of primitive types.

代码如下

Buffer.BlockCopy(bSource, 0, bTarget, 0, SIZE);

跟上面的 Array.Copy 第二个重载一样,需要分别指定两个数组的偏移值

Buffer.MemoryCopy()

这个是 unsafe 方法,需要用到指针 理论上是性能最好的

我最喜欢的就是这个方法(逼格高)

使用 unsafe 代码,请先在编译选项里面开启 allow unsafe code 选项。

这个 MemoryCopy 方法的函数签名是这样的

static unsafe void MemoryCopy(void* source, void* destination, long destinationSizeInBytes, long sourceBytesToCopy)

前两个参数是指针类型,后俩个是长度,注意是bytes字节数,不是数组的元素个数

C#中的byte占8bit,刚好是一个byte,所以直接用元素个数就行,如果是其他类型的数组,得根据类型长度计算字节数,然后再传进去。

代码如下,在函数定义里面加上unsafe关键字以使用 fixed 块和指针

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);
}

然后

我在搜索资料的过程中还发现了有人用了 Buffer.Memcpy 这个方法,但这个是 internal 方法,没有开放,得用黑科技去调用

我折腾了很久,终于搞出了调用非公开方法的代码

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);
}
}

实际测试这个 MemcpyMemoryCopy 的性能是差不多的

看了一下.NetCore的源码

果然,这俩个的实现基本是一样的

// 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 */);
}

另一个

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));
}

这俩最终都是调用的 Memmove 这个方法

区别就是这俩方法的参数不一样了。

benchmark

性能测试结果

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

该用哪个方法来拷贝数组,一目了然了吧~

参考资料

C#高性能数组拷贝实验的更多相关文章

  1. java杂记——数组拷贝

    这里介绍两种java提供的数组拷贝方法: (1)Arrays提供的copyOf(T src, T desLength)和copyOfRange(T src, int from, int to) (2) ...

  2. Java基础知识强化85:System类之arraycopy()方法(数组拷贝)

    1. arraycopy方法(数组拷贝) public static void arraycopy(object src,int srcPos,Object dest,int destPos, int ...

  3. JavaScript 高性能数组去重

    中午和同事吃饭,席间讨论到数组去重这一问题 我立刻就分享了我常用的一个去重方法,随即被老大指出这个方法效率不高 回家后我自己测试了一下,发现那个方法确实很慢 于是就有了这一次的高性能数组去重研究 一. ...

  4. C#编程(七十六)----------使用指针实现基于栈的高性能数组

    使用指针实现基于栈的高性能数组 以一个案例为主来分析实现方法: using System; using System.Collections.Generic; using System.Linq; u ...

  5. Java 数组拷贝方法 System.arraycopy

    System类提供的数组拷贝方法: public static native void arraycopy(Object src, int srcPos, Object dest, int destP ...

  6. Java数组拷贝的五种方法

    在Java中有多种方法可以拷贝一个数组,到另外一个数组. 1.循环拷贝 在循环拷贝方法中,只需要利用i,移动指针即可复制所有数组到arrayB中. for(int i=0;i<arrayA.le ...

  7. js 数组拷贝与深拷贝

    1.对于普通数组(数组元素为数字或者字符串) var _testCopy = [1,2,3].concat();//拷贝数组(浅拷贝) 2.对于对象数组 (深拷贝) //形如var _objArr=[ ...

  8. String 字符串的追加,数组拷贝

    package chengbaoDemo; import java.util.Arrays; /** *需求:数组的扩容以及数据的拷贝 *分析:因为String的实质是以字符数组存储的,所以字符串的追 ...

  9. javascript : 复杂数据结构拷贝实验

    数组深拷贝看起来很简单. array.concat()就行了. 但是,如果数组里有对象呢? 实际上,你以为你拷贝了对象,但实际上你只拷贝了对象的引用(指针)! 我们可以做个试验. // test le ...

  10. 数组拷贝 copyOf()

    Arrarys类的copyof方法与copyOfRange方法可以实现对数组的复制,前者是复制数组到指定的长度,后者将指定的长度复制到一个新数组中. 1.copyOf()方法 该方法提供了很多种重载形 ...

随机推荐

  1. .Net Core中获取appsettings.json中的节点数据

    获取ConnectionStrings节点数据 //appsettings.json { "ConnectionStrings": { //DEV "DbConn&quo ...

  2. 题解UVA10948 The primary problem

    前言 前置 \(\sf{Solution}\) 既然有了 \(n\) ,那找出 \(a\) 和 \(b\) 就只要枚举 \(a\) 的范围 \(1\sim n\),判断 \(a\) 和 \(n-a\) ...

  3. IO模型及高性能网络架构分析

    前言 操作系统一次IO调用过程 应用程序发起的一次IO操作包含两个阶段: IO调用:应用程序进程向操作系统内核发起调用. IO执行:操作系统内核完成IO操作. 操作系统内核完成IO操作还包括两个过程: ...

  4. 【单片机入门】(四)应用层软件开发的单片机学习之路-----ESP32开发板PWM控制电机以及中断的使用

    引言 各位大佬,晚上好啊,在上一篇博客中,我们讲了什么是UART串口通讯,以及使用USB转TTL使得单片机可以和c#上位机做一个串口通讯,接下来,为大家带来PWM的概念原理,以及实际案例,使用PWM对 ...

  5. Go实现常用软件设计模式三:生成器模式

    目录: 举个栗子 概念介绍 使用场景 1.举个栗子 2.概念介绍 使用一个中间件来帮助我们填充创建对象参数 优点: 将创建逻辑集中在一起 复用了不同参数创建逻辑 缺点: 新增生成器类 3.使用场景 m ...

  6. 【Java并发005】原理层面:volatile关键字全解析

    一.前言 在Java 5之前,volatile是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Java 5之后,volatile关键字才得以重获生机. volatile关键字虽然 ...

  7. 【第6篇】AI语音测试简介

    1.4 AI语音测试简介 AI语音测试是针对人工智能语音交互的测试,人工智能语音交互一般包含语音识别技术(ASR).自然语言处理技术(NLP)和语音合成技术(TTS)等3大部分,通俗地来讲AI语音测试 ...

  8. 【GUI开发案例】用python爬百度搜索结果,并开发成exe桌面软件!

    一.背景介绍 你好,我是 @马哥python说 ,一名10年程序猿. 1.1 老版本 之前我开发过一个百度搜索的python爬虫代码,具体如下: [python爬虫案例]用python爬取百度的搜索结 ...

  9. MyEclipse 中自动安插作者、注释日期等快捷键方法

    MyEclipse 中自动插入作者.注释日期等快捷键方法 MyEclipse 中自动插入作者.注释日期等de快捷键方法依次打开然后找到 Window -->Preferences->Jav ...

  10. day30-JQuery03

    JQuery03 4.jQuery选择器03 4.4表单选择器 应用实例 <!DOCTYPE html> <html lang="en"> <head ...