博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
.Net 5性能改进
阅读量:4033 次
发布时间:2019-05-24

本文共 10604 字,大约阅读时间需要 35 分钟。

起因

在.Net Core跳过4.0,避免和先.Net Framework 4.0同名,版本号变为5.0,同时也不在叫.Net Core改为.Net 5(统一的叫法),先看看官方对.Net版本规划.

本文主要是根据https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-5/ 翻译而来.不完全翻译.顺序也有所调整.

从CPU平台看.Net 5改进

在.Net 5 开始使用Arm64指令集进行性能优化,这对国产飞腾和华为鲲鹏服务器,在性能上是有很大的提升.在有就是国产龙芯处理器开始在.Net Core 3.1进行支持,不知道在.Net 5正式发布前.龙芯指令集的代码会不会合并到.Net 5代码的主干中.(编者朱:这个可能性是没有了,.NET 6是很有可能的)

从功能上看.Net 5改进

GC

GC对性能的影响还是很大的.是因为GC回收资源的时候会挂起工作线程,只留GC线程清理资源和回收内存,造成程序有短暂的停顿.

如何提高GC性能:

  1. 减少内存分配,就能减少GC回收的次数

  2. 减少GC线程挂起的时间.让工作线程一直在执行任务(说白点就是让工作线程一直处于干活的状态)

在.Net 5 GC改进:

  1. 在Server GC中增加均衡/平衡机制(Balance),给每个GC线程一样多的工作量(理论上),每个GC线程执行的时间也是一样的.避免某个GC线程一直在工作,其他GC线程没有任务可执行.从而缩短GC线程挂起的时间. 有专门说均衡机制的文章https://devblogs.microsoft.com/dotnet/balancing-work-on-gc-threads/文章 

  2. 减少 第0代(gen0)和第1代(gen1)回收次数

  3. 减少GC扫描静态数据和减少使用并发锁

  4. 从CoreCLR(c/c++代码) 部分代码(如Array.Sort)移植到System.Private.Corelib(C#代码),这样的好处,就是代码复用(CoreCLR和Mono共用一个实现),c#代码是安全的(相对于c语言,如数组越界等),可以更好的优化C#代码.

关于GC示例1代码:

using System;using System.Diagnostics;using System.Threading;class Program{    public static void Main()    {        new Thread(() =>        {            var a = new int[20];            while (true) Array.Sort(a);        }) { IsBackground = true }.Start();        var sw = new Stopwatch();        while (true)        {            sw.Restart();            for (int i = 0; i < 10; i++)            {                GC.Collect();                Thread.Sleep(15);            }            Console.WriteLine(sw.Elapsed.TotalSeconds);        }    }}

关于GC示例2代码:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using BenchmarkDotNet.Attributes;using BenchmarkDotNet.Diagnosers;using BenchmarkDotNet.Running;namespace dotnet_perf{    public class DoubleSorting : Sorting
{ protected override double GetNext() => _random.Next(); } public class Int32Sorting : Sorting
{ protected override int GetNext() => _random.Next(); } public class StringSorting : Sorting
{ protected override string GetNext() { var dest = new char[_random.Next(1, 5)]; for (int i = 0; i < dest.Length; i++) dest[i] = (char)('a' + _random.Next(26)); return new string(dest); } } public abstract class Sorting
{ protected Random _random; private T[] _orig, _array; [Params(10)] public int Size { get; set; } protected abstract T GetNext(); [GlobalSetup] public void Setup() { _random = new Random(42); _orig = Enumerable.Range(0, Size).Select(_ => GetNext()).ToArray(); _array = (T[])_orig.Clone(); Array.Sort(_array); } [Benchmark] public void Random() { _orig.AsSpan().CopyTo(_array); Array.Sort(_array); } }}

JIT改进

JIT(即时编译器,也有人称实时编译器).作用就是C#/Vb.Net代码(编译后生成IL代码,CPU是不认识什么是IL代码的),在运行的时候,JIT生成汇编代码(或者叫机器指令),再有CPU去执行.

JIT这里有两个作用:

  1. 安全检查,说C#/VB.Net是安全的语言,第一是编译的时候,对代码进行安全检查.第二是在程序运行的时候,JIT也会进行安全检查.

  2. 生成汇编代码.

JIT对程序的性能也有很大的比重.所以要求JIT生成性能更高,代码更少的指令(通常情况下汇编指令越少,性能越高,但不是绝对的,比如使用CPU自带的指令).

C#和Java跨平台是都有中间语言的存在(.Net的IL和Java的ByteCode),这里的平台指CPU架构,CPU架构分为CISC(复杂指令集,代表为X86)和RISC(精简指令集,代表为ARM和国产龙芯),在JIT将中间语言生成对应的平台的指令.

示例1:

using System;using BenchmarkDotNet.Attributes;namespace dotnet_perf{    public class TestJit    {        private B[] _array = new B[42];        [Benchmark]        public int Ctor() => new Span(_array).Length;    }    class A    {    }    sealed class B : A    {    }}

汇编代码对比:

.NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT

; dotnet_perf.TestJit.Ctor();         public int Ctor() => new Span(_array).Length;;                              ^^^^^^^^^^^^^^^^^^^^^^^^^^       push      rdi       push      rsi       sub       rsp,28       mov       rsi,[rcx+8]       test      rsi,rsi       jne       short M00_L00       xor       eax,eax       jmp       short M00_L01M00_L00:       mov       rcx,rsi       call      00007FF884C41F50       mov       rdi,rax       mov       rcx,7FF82531DEAA       call      CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE       cmp       rdi,rax       jne       short M00_L02       mov       eax,[rsi+8]M00_L01:       add       rsp,28       pop       rsi       pop       rdi       retM00_L02:       call      System.ThrowHelper.ThrowArrayTypeMismatchException()       int       3; Total bytes of code 66

.NET Core 5.0.0 (CoreCLR 5.0.20.47505, CoreFX 5.0.20.47505), X64 RyuJIT

; dotnet_perf.TestJit.Ctor();         public int Ctor() => new Span(_array).Length;;                              ^^^^^^^^^^^^^^^^^^^^^^^^^^       mov       rax,[rcx+8]       test      rax,rax       jne       short M00_L00       xor       eax,eax       jmp       short M00_L01M00_L00:       mov       eax,[rax+8]M00_L01:       ret; Total bytes of code 17

从上方的汇编代码对比,发现.Net 5生成的汇编代码更少,从执行时间来看,.Net 5生成的代码性能更高.

Intrinsics(内部函数,也有称内联函数,这里翻译为指令)

Intrinsics为什么这里要翻译为指令,是因为Intrinsics函数都是在指令集,如X86的AVX/SSE等.

说起这个Intrinsics就得说SIMD(Single Instruction Multiple Data,即单指令流多数据流).

代码:

using System.Numerics;using BenchmarkDotNet.Attributes;namespace App_Pef5{    [DisassemblyDiagnoser(printSource: true)]    //[RyuJitX64Job]    public class Intrinsics    {        [Benchmark]        public void T1()        {            double[] op1 = new double[] { 1.0, 2.0, 3.0, 4.0 };            double[] op2 = new double[] { 1.0, 2.0, 3.0, 4.0 };            double[] result = new double[4];            for (int i = 0; i < 10000; i++)            {                var v1 = new Vector
(op1, 0); var v2 = new Vector
(op2, 0); var v3 = Vector.Add(v1, v2); v3.TryCopyTo(result); } } [Benchmark] public void T2() { double[] op1 = new double[] { 1.0, 2.0, 3.0, 4.0 }; double[] op2 = new double[] { 1.0, 2.0, 3.0, 4.0 }; double[] result = new double[4]; for (int j = 0; j < 10000; j++) { for (int i = 0; i < op1.Length; i++) { result[i] = op1[i] + op2[i]; } } } }}

T1函数生成汇编代码:

; App_Pef5.Intrinsics.T1()       push      rdi       push      rsi       sub       rsp,28       vzeroupper;             double[] op1 = new double[] { 1.0, 2.0, 3.0, 4.0 };;             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^       mov       rcx,offset MT_System.Double[]       mov       edx,4       call      CORINFO_HELP_NEWARR_1_VC       mov       rsi,rax       mov       rcx,14C58332BE0       vmovdqu   xmm0,xmmword ptr [rcx]       vmovdqu   xmmword ptr [rsi+10],xmm0       vmovdqu   xmm0,xmmword ptr [rcx+10]       vmovdqu   xmmword ptr [rsi+20],xmm0;             double[] op2 = new double[] { 1.0, 2.0, 3.0, 4.0 };;             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^       mov       rcx,offset MT_System.Double[]       mov       edx,4       call      CORINFO_HELP_NEWARR_1_VC       mov       rdi,rax       mov       rcx,14C58332BE0       vmovdqu   xmm0,xmmword ptr [rcx]       vmovdqu   xmmword ptr [rdi+10],xmm0       vmovdqu   xmm0,xmmword ptr [rcx+10]       vmovdqu   xmmword ptr [rdi+20],xmm0;             double[] result = new double[4];;             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^       mov       rcx,offset MT_System.Double[]       mov       edx,4       call      CORINFO_HELP_NEWARR_1_VC;             for (int i = 0; i < 10000; i++);                  ^^^^^^^^^       xor       edx,edx;                 var v1 = new Vector
(op1, 0);; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^M00_L00: vmovupd ymm0,[rsi+10]; var v2 = new Vector
(op2, 0);; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ vmovupd ymm1,[rdi+10] vaddpd ymm0,ymm0,ymm1; v3.TryCopyTo(result);; ^^^^^^^^^^^^^^^^^^^^^ lea rcx,[rax+10] mov r8d,4 cmp r8d,4 jb short M00_L01 vmovupd [rcx],ymm0M00_L01: inc edx cmp edx,2710 jl short M00_L00 vzeroupper add rsp,28 pop rsi pop rdi ret; Total bytes of code 189

T2函数生成汇编代码:

; App_Pef5.Intrinsics.T2()       push      rdi       push      rsi       sub       rsp,28       vzeroupper;             double[] op1 = new double[] { 1.0, 2.0, 3.0, 4.0 };;             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^       mov       rcx,offset MT_System.Double[]       mov       edx,4       call      CORINFO_HELP_NEWARR_1_VC       mov       rsi,rax       mov       rcx,212ECBD2BE0       vmovdqu   xmm0,xmmword ptr [rcx]       vmovdqu   xmmword ptr [rsi+10],xmm0       vmovdqu   xmm0,xmmword ptr [rcx+10]       vmovdqu   xmmword ptr [rsi+20],xmm0;             double[] op2 = new double[] { 1.0, 2.0, 3.0, 4.0 };;             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^       mov       rcx,offset MT_System.Double[]       mov       edx,4       call      CORINFO_HELP_NEWARR_1_VC       mov       rdi,rax       mov       rcx,212ECBD2BE0       vmovdqu   xmm0,xmmword ptr [rcx]       vmovdqu   xmmword ptr [rdi+10],xmm0       vmovdqu   xmm0,xmmword ptr [rcx+10]       vmovdqu   xmmword ptr [rdi+20],xmm0;             double[] result = new double[4];;             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^       mov       rcx,offset MT_System.Double[]       mov       edx,4       call      CORINFO_HELP_NEWARR_1_VC;             for (int j = 0; j < 10000; j++);                  ^^^^^^^^^       xor       edx,edx;                 for (int i = 0; i < op1.Length; i++);                      ^^^^^^^^^M00_L00:       xor       ecx,ecx;                     result[i] = op1[i] + op2[i];;                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^M00_L01:       movsxd    r8,ecx       vmovsd    xmm0,qword ptr [rsi+r8*8+10]       vaddsd    xmm0,xmm0,qword ptr [rdi+r8*8+10]       vmovsd    qword ptr [rax+r8*8+10],xmm0       inc       ecx       cmp       ecx,4       jl        short M00_L01       inc       edx       cmp       edx,2710       jl        short M00_L00       add       rsp,28       pop       rsi       pop       rdi       ret; Total bytes of code 185

使用intrinsics指令,单次并不会带来性能的提升,需要在多次使用的时候,才能带来更好的性能,因为上面的代码,是我首次使用intrinsics,后面在去了解C/C++中是如何使用的.在去整体对比性能.

从细节上看有哪些改进

  • 更快的加载程序集,在.Net Core时,程序集被拆分的很多且很小的,加载很多很小的是会增加开销,在.Net 5中通过合并程序集,减少开销.

  • 更快的数学库(算法).

  1. 改进NaN检查.生成更小更快的代码.

  2. SSE和AMD64 (Intrinsics为内部函数) 

  3. 改进哈希值

  • 更快的加密,如RSA.

  • 更快的P/Invoke操作,Windows和Linux

  • 更快的reflection emit

  • 更快的I/O操作,

  • 更少的内存分配.

    1. 减少一些字符串内存分配

    2. 减少一些装箱操作

    3. 删除一些临时内存分配

转载地址:http://dpkdi.baihongyu.com/

你可能感兴趣的文章
mysql:sql truncate (清除表数据)
查看>>
scrapy:xpath string(.)非常注意问题
查看>>
yuv to rgb 转换失败呀。天呀。谁来帮帮我呀。
查看>>
yuv420 format
查看>>
YUV420只绘制Y通道
查看>>
yuv420 还原为RGB图像
查看>>
LED恒流驱动芯片
查看>>
驱动TFT要SDRAM做为显示缓存
查看>>
使用file查看可执行文件的平台性,x86 or arm ?
查看>>
qt5 everywhere 编译summary
查看>>
qt5 everywhere编译完成后,找不到qmake
查看>>
arm-linux开机读取硬件时钟,设置系统时钟。
查看>>
交叉编译在x86上调试好的qt程序
查看>>
/dev/input/event0 键盘输入
查看>>
qt 创建异形窗体
查看>>
可重入函数与不可重入函数
查看>>
简单Linux C线程池
查看>>
内存池
查看>>
输入设备节点自动生成
查看>>
opencv test code-1
查看>>