本文来对比多个不同的方法进行数组拷贝,和测试其性能

测试性能必须采用基准(标准)性能测试方法,否则测试结果不可信。在 dotnet 里面,可以采用 BenchmarkDotNet 进行性能测试。详细请看 C# 标准性能测试

拷贝某个数组的从某个起始点加上某个长度的数据到另一个数组里面,可选方法有很多,本文仅列举出使用 for 循环拷贝,和使用 Array.Copy 方法和用 Span 方法进行拷贝进行对比

假定有需要被拷贝的数组是 TestData 其定义如下

        static Program()
{
TestData = new int[1000];
for (int i = 0; i < 1000; i++)
{
TestData[i] = i;
}
} private static readonly int[] TestData;

使用 for 循环拷贝的方法如下

        public object CopyByFor(int start, int length)
{
var rawPacketData = TestData; var data = new int[length];
for (int localIndex = 0, rawArrayIndex = start; localIndex < data.Length; localIndex++, rawArrayIndex++)
{
data[localIndex] = rawPacketData[rawArrayIndex];
}
return data;
}

以上代码返回 data 作为 object 仅仅只是为了做性能测试,避免被 dotnet 优化掉

另一个拷贝数组是采用 Array.Copy 拷贝,逻辑如下

        public object CopyByArray(int start, int length)
{
var rawPacketData = TestData;
var data = new int[length];
Array.Copy(rawPacketData,start,data,0, length);
return data;
}

采用新的 dotnet 提供的 Span 进行拷贝,代码如下

        public object CopyBySpan(int start, int length)
{
var rawPacketData = TestData;
var rawArrayStartIndex = start;
var data = rawPacketData.AsSpan(rawArrayStartIndex, length).ToArray();
return data;
}

接着加上一些性能调试辅助逻辑

        [Benchmark]
[ArgumentsSource(nameof(ProvideArguments))]
public object CopyByFor(int start, int length)
{
var rawPacketData = TestData; var data = new int[length];
for (int localIndex = 0, rawArrayIndex = start; localIndex < data.Length; localIndex++, rawArrayIndex++)
{
data[localIndex] = rawPacketData[rawArrayIndex];
}
return data;
} [Benchmark]
[ArgumentsSource(nameof(ProvideArguments))]
public object CopyByArray(int start, int length)
{
var rawPacketData = TestData;
var data = new int[length];
Array.Copy(rawPacketData,start,data,0, length);
return data;
} public IEnumerable<object[]> ProvideArguments()
{
foreach (var start in new[] { 0, 10, 100 })
{
foreach (var length in new[] { 10, 20, 100 })
{
yield return new object[] { start, length };
}
}
}

在我的设备上的测试效果如下

BenchmarkDotNet=v0.13.1, OS=Windows 10.0.19042.1200 (20H2/October2020Update)
Intel Core i7-9700K CPU 3.60GHz (Coffee Lake), 1 CPU, 8 logical and 8 physical cores
.NET SDK=6.0.100-preview.7.21379.14
[Host] : .NET 6.0.0 (6.0.21.37719), X64 RyuJIT
DefaultJob : .NET 6.0.0 (6.0.21.37719), X64 RyuJIT

可以看到,在对比使用 for 循环拷贝和使用 Array.Copy 拷贝中,使用 Array.Copy 拷贝的性能更好,在拷贝的数组长度越长的时候,使用 Array.Copy 拷贝性能优势就更好

接下来再加上 Span 的性能比较,如下面代码

        [Benchmark]
[ArgumentsSource(nameof(ProvideArguments))]
public object CopyBySpan(int start, int length)
{
var rawPacketData = TestData;
var rawArrayStartIndex = start;
var data = rawPacketData.AsSpan(rawArrayStartIndex, length).ToArray();
return data;
}

性能对比测试如下

可以看到 Span 的性能比 Array.Copy 拷贝性能更强

在 Span 里面,转换为数组的逻辑如下

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
public T[] ToArray()
{
if (_length == 0)
return Array.Empty<t>(); var destination = new T[_length];
Buffer.Memmove(ref MemoryMarshal.GetArrayDataReference(destination), ref _pointer.Value, (nuint)_length);
return destination;
}

这里使用到的 Buffer 的有黑科技的 Memmove 方法,此方法的实现如下

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void Memmove<t>(ref T destination, ref T source, nuint elementCount)
{
if (!RuntimeHelpers.IsReferenceOrContainsReferences<t>())
{
// Blittable memmove Memmove(
ref Unsafe.As<t, byte="">(ref destination),
ref Unsafe.As<t, byte="">(ref source),
elementCount * (nuint)Unsafe.SizeOf<t>());
}
else
{
// Non-blittable memmove
BulkMoveWithWriteBarrier(
ref Unsafe.As<t, byte="">(ref destination),
ref Unsafe.As<t, byte="">(ref source),
elementCount * (nuint)Unsafe.SizeOf<t>());
}
}

以上性能测试使用的是 int 数组,刚好能进入 Memmove 的分支,而不是 BulkMoveWithWriteBarrier 这个分支。在里层的 Memmove 方法里面用到了很多黑科技,本文只是用来对比多个方法拷贝数组的性能,黑科技部分就需要大家自己去阅读 dotnet 的源代码啦

另外,如果需要做完全的数组的拷贝,数组里面存放的是值类型对象,如 int 类型,那么拷贝整个数组还有另一个可选项是通过 Clone 方法进行拷贝,代码如下

        public object CopyByClone()
{
var data = (int[]) TestData.Clone();
return data;
}

使用 Clone 的方法的行为是返回数组的浅表拷贝,也就是说数组里面的元素没有做深拷贝,只是拷贝数组本身而已。对于值类型来说,就没有啥问题了

稍微更改一下性能测试,更改的代码如下

    [MemoryDiagnoser]
public class Program
{
static void Main(string[] args)
{
BenchmarkRunner.Run<program>();
} static Program()
{
TestData = new int[1000];
for (int i = 0; i < 1000; i++)
{
TestData[i] = i;
}
} [Benchmark]
public object CopyByFor()
{
var rawPacketData = TestData;
var length = TestData.Length; var data = new int[length];
for (int localIndex = 0, rawArrayIndex = 0; localIndex < data.Length; localIndex++, rawArrayIndex++)
{
data[localIndex] = rawPacketData[rawArrayIndex];
}
return data;
} [Benchmark]
public object CopyByArray()
{
var length = TestData.Length;
var start = 0; var rawPacketData = TestData;
var data = new int[length];
Array.Copy(rawPacketData,start,data,0, length);
return data;
} [Benchmark]
public object CopyByClone()
{
var data = (int[]) TestData.Clone();
return data;
} private static readonly int[] TestData;
}

通过下图可以了解到采用 Clone 方法和采用 Array.Copy 方法的性能差不多,但 Clone 稍微快一点

以上是给 WPF 框架做性能优化时测试的,详细请看

特别感谢ThomasGoulet73大佬教我使用 AsSpan 的方法拷贝数组

dotnet 6 数组拷贝性能对比的更多相关文章

  1. list 、set 、map 粗浅性能对比分析

    list .set .map 粗浅性能对比分析   不知道有多少同学和我一样,工作五年了还没有仔细看过list.set的源码,一直停留在老师教导的:"LinkedList插入性能比Array ...

  2. ArrayList和LinkedList的几种循环遍历方式及性能对比分析(转)

    主要介绍ArrayList和LinkedList这两种list的五种循环遍历方式,各种方式的性能测试对比,根据ArrayList和LinkedList的源码实现分析性能结果,总结结论. 通过本文你可以 ...

  3. ArrayList和LinkedList的几种循环遍历方式及性能对比分析

    最新最准确内容建议直接访问原文:ArrayList和LinkedList的几种循环遍历方式及性能对比分析 主要介绍ArrayList和LinkedList这两种list的五种循环遍历方式,各种方式的性 ...

  4. PHP生成随机密码的4种方法及性能对比

    PHP生成随机密码的4种方法及性能对比 http://www.php100.com/html/it/biancheng/2015/0422/8926.html 来源:露兜博客   时间:2015-04 ...

  5. ArrayList和LinkedList的几种循环遍历方式及性能对比分析(转载)

    原文地址: http://www.trinea.cn/android/arraylist-linkedlist-loop-performance/ 原文地址: http://www.trinea.cn ...

  6. HashMap循环遍历方式及其性能对比(zhuan)

    http://www.trinea.cn/android/hashmap-loop-performance/ ********************************************* ...

  7. ArrayList和LinkedList遍历方式及性能对比分析

    ArrayList和LinkedList的几种循环遍历方式及性能对比分析 主要介绍ArrayList和LinkedList这两种list的五种循环遍历方式,各种方式的性能测试对比,根据ArrayLis ...

  8. [java]序列化框架性能对比(kryo、hessian、java、protostuff)

    序列化框架性能对比(kryo.hessian.java.protostuff) 简介:   优点 缺点 Kryo 速度快,序列化后体积小 跨语言支持较复杂 Hessian 默认支持跨语言 较慢 Pro ...

  9. HashMap循环遍历方式及其性能对比

    主要介绍HashMap的四种循环遍历方式,各种方式的性能测试对比,根据HashMap的源码实现分析性能结果,总结结论.   1. Map的四种遍历方式 下面只是简单介绍各种遍历示例(以HashMap为 ...

  10. 【转】ArrayList和LinkedList的几种循环遍历方式及性能对比分析

    原文网址:http://www.trinea.cn/android/arraylist-linkedlist-loop-performance/ 主要介绍ArrayList和LinkedList这两种 ...

随机推荐

  1. PS-AXI-GPIO-流水灯设计

    PS-AXI-GPIO-流水灯设计 1.实验目的 在了解了AXI协议的基本内容后,通过已经设计好的AXI的IP核来了解实际设计中AXI的工作原理和设计原理是必要的.这个实验以前实际上按照教程做过,但是 ...

  2. KingbaseESV8R6使用pageinspect插件观察空值

    前言 在KingbaseES元组头数据中,有一个t_bits数组,用于存储空值位图.当元组中没有null值的时候,t_bits是空的,当元组有null值的列时,t_bits使用一个bit来表示列是否为 ...

  3. KingbaseES V8R6集群运维案例之---访问系统表unrecognized token- false故障

    KingbaseES V8R6集群运维案例之---访问系统表'unrecognized token: "false"'故障 案例说明: KingbaseES V8R6集群在升级补丁 ...

  4. KingbaseES V8R6 常用的系统函数

    查看当前日志文件lsn位置: select sys_current_wal_lsn(); 查看某个lsn对应的日志名: select sys_walfile_name('0/1162FBA0'); 查 ...

  5. C++原子操作与内存序 1

    问题 #include<iostream> #include<thread> int main() { int sum = 0; auto f = [&sum]() { ...

  6. #贪心#CF840A Leha and Function

    题目 设 \(f(n,k)\) 表示 区间 \([1,n]\) 选出 \(k\) 个元素的集合的期望最小值, 现在需要重排 \(a\) 数组,使得 \(\sum_{i=1}^mf(a_i,b_i)\) ...

  7. OpenHarmony中的HDF单链表及其迭代器

    概念 为了性能考虑,嵌入式系统一般使用C语言进行开发,由于C语言标准库没有封装链表,所以嵌入式系统一般自己设计和实现链表这种数据结构.单链表是链表中的一种,本文描述OpenAtom OpenHarmo ...

  8. Pdfium.Net.Free 一个免费的Pdfium的 .net包装器--可视化编辑pdf

    Pdfium.Net.Free 支持 .NETFramework 4.0 .NETFramework 4.5 .NETStandard 2.0 .Net8.0 可以和PdfiumViewer.Free ...

  9. [一本通1677/JZOJ1217/CJOJ1101]软件开发 题解

    题目描述 一个软件开发公司同时要开发两个软件,并且要同时交付给用户,现在公司为了尽快完成这一任务,将每个软件划分成\(m\)个模块,由公司里的技术人员分工完成,每个技术人员完成同一软件的不同模块的所用 ...

  10. openGauss数据库xlog目录满问题处理

    openGauss 数据库 xlog 目录满问题处理 openGauss 数据库 xlog 满通常为以下几个原因: 1.主备状态不正常,存在网络问题,集群内有宕机的节点 2.xlog 保留数量过多 3 ...