C#高性能数组拷贝实验
前言
昨天 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);
    }
}
实际测试这个 Memcpy 和 MemoryCopy 的性能是差不多的
看了一下.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
该用哪个方法来拷贝数组,一目了然了吧~
参考资料
- 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
 
C#高性能数组拷贝实验的更多相关文章
- java杂记——数组拷贝
		
这里介绍两种java提供的数组拷贝方法: (1)Arrays提供的copyOf(T src, T desLength)和copyOfRange(T src, int from, int to) (2) ...
 - Java基础知识强化85:System类之arraycopy()方法(数组拷贝)
		
1. arraycopy方法(数组拷贝) public static void arraycopy(object src,int srcPos,Object dest,int destPos, int ...
 - JavaScript 高性能数组去重
		
中午和同事吃饭,席间讨论到数组去重这一问题 我立刻就分享了我常用的一个去重方法,随即被老大指出这个方法效率不高 回家后我自己测试了一下,发现那个方法确实很慢 于是就有了这一次的高性能数组去重研究 一. ...
 - C#编程(七十六)----------使用指针实现基于栈的高性能数组
		
使用指针实现基于栈的高性能数组 以一个案例为主来分析实现方法: using System; using System.Collections.Generic; using System.Linq; u ...
 - Java 数组拷贝方法 System.arraycopy
		
System类提供的数组拷贝方法: public static native void arraycopy(Object src, int srcPos, Object dest, int destP ...
 - Java数组拷贝的五种方法
		
在Java中有多种方法可以拷贝一个数组,到另外一个数组. 1.循环拷贝 在循环拷贝方法中,只需要利用i,移动指针即可复制所有数组到arrayB中. for(int i=0;i<arrayA.le ...
 - js 数组拷贝与深拷贝
		
1.对于普通数组(数组元素为数字或者字符串) var _testCopy = [1,2,3].concat();//拷贝数组(浅拷贝) 2.对于对象数组 (深拷贝) //形如var _objArr=[ ...
 - String 字符串的追加,数组拷贝
		
package chengbaoDemo; import java.util.Arrays; /** *需求:数组的扩容以及数据的拷贝 *分析:因为String的实质是以字符数组存储的,所以字符串的追 ...
 - javascript : 复杂数据结构拷贝实验
		
数组深拷贝看起来很简单. array.concat()就行了. 但是,如果数组里有对象呢? 实际上,你以为你拷贝了对象,但实际上你只拷贝了对象的引用(指针)! 我们可以做个试验. // test le ...
 - 数组拷贝 copyOf()
		
Arrarys类的copyof方法与copyOfRange方法可以实现对数组的复制,前者是复制数组到指定的长度,后者将指定的长度复制到一个新数组中. 1.copyOf()方法 该方法提供了很多种重载形 ...
 
随机推荐
- 三十一、kubernetes网络介绍
			
Kubernetes 网络介绍 Service是Kubernetes的核心概念,通过创建Service,可以为一组具有相同功能的容器应用提供一个统一的入口地址,并且将请求负载分发到后端的各个容器应用上 ...
 - 漫谈Entity-Component-System
			
原文链接 简介 对于很多人来说,ECS只是一个可以提升性能的架构,但是我觉得ECS更强大的地方在于可以降低代码复杂度. 在游戏项目开发的过程中,一般会使用OOP的设计方式让GameObject处理自身 ...
 - Python 多重继承时metaclass conflict问题解决与原理探究
			
背景 最近有一个需求需要自定义一个多继承abc.ABC与django.contrib.admin.ModelAdmin两个父类的抽象子类,方便不同模块复用大部分代码,同时强制必须实现所有抽象方法,没想 ...
 - 解决oracle18c没有hr用户
			
1.查找系统变量ORACLE_HOME的值 2.按照路径寻找sql文件 ORACLE_HOME变量值+demo\schema\human_resources 3.把hr_main.sql脚本文件放在此 ...
 - 网页状态码(HTTP状态码)。
			
网页状态码(HTTP状态码). 状态码 说明 详情 100 继续 请求者应当继续提出请求.服务器已收到请求的一部分,正在等待其余部分. 101 切换协议 请求者已要求服务器切换协议,服务器已确认并准备 ...
 - android 代码分析
			
1.@Override注解 @Override 注解是用来指定方法重写的,只能修饰方法并且只能用于方法重写,不能修饰其它的元素. 作用是告诉编译器检查这个方法,保证父类要包含一个被该方法重写的方法,否 ...
 - Windows版CheatSheet——一键显示当前程序快捷键列表
			
Windows系统上的各种软件有太多的快捷键,想要记住是几乎不可能的,推荐一个一键显示当前软件快捷键的软件,在使用其他程序的时候,只要按下Ctrl+`就可以理解弹出该软件的所有快捷键列表,还支持收藏功 ...
 - Anaconda环境搭配(Ipython)-获得jupyter notebook(适用Win10)
			
关于如何下载anaconda并获得jupyter notebook的随笔. 首先下载anaconda,然后下载完成后,如果是win10系统,则通过下图的放大镜搜索Jupyter Notebook 会有 ...
 - mindxdl--common--k8s_utils.go
			
// Copyright (c) 2021. Huawei Technologies Co., Ltd. All rights reserved.// Package common define co ...
 - linux下进程的实际用户ID(有效组)和有效用户ID(有效组ID)
			
实际用户ID(实际组ID):标识当前用户(所属组)是谁,当用户登陆时取自口令文件. 有效用户ID(有效组ID):用来决定我们(当前进程)对文件的访问权(即实际该进程的是以那个用户运行的). 一般情况下 ...