.NET8极致性能优化Reflection

您所在的位置:网站首页 反射缓存 .NET8极致性能优化Reflection

.NET8极致性能优化Reflection

2024-07-03 07:27:38| 来源: 网络整理| 查看: 265

点击上方蓝字 江湖评谈关注我

b7609cb0f526e09327f18b51a05ea164.png

前言

反射一直是性能的瓶颈,所以无论哪个.NET版本反射的优化必然少不了。主要是集中在两个方面优化,分配和缓存。.NET8自然也不例外。本篇看下。

概述

比如针对GetCustomAttributes 通过反射获取属性的优化,以下例子

// dotnet run -c Release -f net7.0 --filter "*" --runtimes net7.0 net8.0 public class Tests { public object[] GetCustomAttributes() => typeof(C).GetCustomAttributes(typeof(MyAttribute), inherit: true); [My(Value1 = 1, Value2 = 2)] class C { } [AttributeUsage(AttributeTargets.All)] public class MyAttribute : Attribute { public int Value1 { get; set; } public int Value2 { get; set; } } }

.NET7和.NET8明显的差异,它主要是优化了避免分配一个object[1]数组来设置属性的值

方法运行时平均值比率分配分配比率GetCustomAttributes.NET 7.01,287.1 ns1.00296 B1.00GetCustomAttributes.NET 8.0994.0 ns0.77232 B0.78

其它的比如减少反射堆栈中的分配,比如通过更自由的spans。改进了Type上的泛型处理,从而提升各种与泛型相关的成员性能,比如GetGenericTypeDefinition,它的结果现在被缓存在了Type对象上

// dotnet run -c Release -f net7.0 --filter "*" --runtimes net7.0 net8.0 public class Tests { private readonly Type _type = typeof(List);     public Type GetGenericTypeDefinition() => _type.GetGenericTypeDefinition(); }

.NET7和.NET8如下

方法运行时平均值比GetGenericTypeDefinition.NET 7.047.426 ns1.00GetGenericTypeDefinition.NET 8.03.289 ns0.07

这些都是细枝末节,影响反射性能最大的一块是MethodBase.Invoke。当在编译的时候,知道方法的签名并且通过反射来调用方法。就可以通过使用CreateDelegate来获取和缓存该方法的委托,然后通过该委托执行所有的调用。从而实现性能最佳化,但是如果在编译的时候你不知道方法的签名,则需要依赖动态的方法。比如MethodBase.Invoke,这个方法降低性能并且更耗时。一些比较了解.NET开发的人员会用 emit避免这种开销。.NET7里面采用这种方式。.NET8里面,为许多这样的情况进行了改进,以前,emitter 总是生成可以容纳 ref/out 参数的代码,但许多方法不提供这样的参数,当不需要考虑这些因素时,生成的代码可以更高效。

// If you have .NET 6 installed, you can update the csproj to include a net6.0 in the target frameworks, and then run: // dotnet run -c Release -f net6.0 --filter "*" --runtimes net6.0 net7.0 net8.0 // Otherwise, you can run: // dotnet run -c Release -f net7.0 --filter "*" --runtimes net7.0 net8.0 using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running; using System.Reflection; BenchmarkSwitcher.FromAssembly(typeof(Tests).Assembly).Run(args); [HideColumns("Error", "StdDev", "Median", "RatioSD")] public class Tests { private MethodInfo _method0, _method1, _method2, _method3; private readonly object[] _args1 = new object[] { 1 }; private readonly object[] _args2 = new object[] { 2, 3 }; private readonly object[] _args3 = new object[] { 4, 5, 6 }; [GlobalSetup] public void Setup() { _method0 = typeof(Tests).GetMethod("MyMethod0", BindingFlags.NonPublic | BindingFlags.Static); _method1 = typeof(Tests).GetMethod("MyMethod1", BindingFlags.NonPublic | BindingFlags.Static); _method2 = typeof(Tests).GetMethod("MyMethod2", BindingFlags.NonPublic | BindingFlags.Static); _method3 = typeof(Tests).GetMethod("MyMethod3", BindingFlags.NonPublic | BindingFlags.Static); } [Benchmark] public void Method0() => _method0.Invoke(null, null); [Benchmark] public void Method1() => _method1.Invoke(null, _args1); [Benchmark] public void Method2() => _method2.Invoke(null, _args2); [Benchmark] public void Method3() => _method3.Invoke(null, _args3); private static void MyMethod0() { } private static void MyMethod1(int arg1) { } private static void MyMethod2(int arg1, int arg2) { } private static void MyMethod3(int arg1, int arg2, int arg3) { } }

.NET6以及7和8的情况分别如下:

方法运行时平均值比率Method0.NET 6.091.457 ns1.00Method0.NET 7.07.205 ns0.08Method0.NET 8.05.719 ns0.06Method1.NET 6.0132.832 ns1.00Method1.NET 7.026.151 ns0.20Method1.NET 8.021.602 ns0.16Method2.NET 6.0172.224 ns1.00Method2.NET 7.037.937 ns0.22Method2.NET 8.026.951 ns0.16Method3.NET 6.0211.247 ns1.00Method3.NET 7.042.988 ns0.20Method3.NET 8.034.112 ns0.16

这里有一些问题,每次调用都会涉及到一些性能开销,每次调用都会重复。如果我们可以提取这些重复性的工作,对它们进行缓存。就可以实现更好的性能。.NET8里面通过 MethodInvoker 和 ConstructorInvoker 类型中实现了这些功能。这些并没有包含所有 MethodBase.Invoke 处理的不常见错误(如特别识别和处理 Type.Missing),但对于其他所有情况,它为优化在构建时未知签名的方法的重复调用提供了一个很好的解决方案。

// dotnet run -c Release -f net8.0 --filter "*" using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running; using System.Reflection; BenchmarkSwitcher.FromAssembly(typeof(Tests).Assembly).Run(args); [HideColumns("Error", "StdDev", "Median", "RatioSD")] public class Tests { private readonly object _arg0 = 4, _arg1 = 5, _arg2 = 6; private readonly object[] _args3 = new object[] { 4, 5, 6 }; private MethodInfo _method3; private MethodInvoker _method3Invoker; [GlobalSetup] public void Setup() { _method3 = typeof(Tests).GetMethod("MyMethod3", BindingFlags.NonPublic | BindingFlags.Static); _method3Invoker = MethodInvoker.Create(_method3); } [Benchmark(Baseline = true)] public void MethodBaseInvoke() => _method3.Invoke(null, _args3); [Benchmark] public void MethodInvokerInvoke() => _method3Invoker.Invoke(null, _arg0, _arg1, _arg2); private static void MyMethod3(int arg1, int arg2, int arg3) { } }

.NET8的情况如下

方法平均值比率MethodBaseInvoke32.42 ns1.00MethodInvokerInvoke11.47 ns0.35

这些类型被 Microsoft.Extensions.DependencyInjection.Abstractions 中的 ActivatorUtilities.CreateFactory 方法使用,以进一步提高 DI 服务构建性能。通过添加额外的缓存层进一步改进,进一步避免每次构建时的反射。e10841c82186d600f6c73e63079c5c45.jpeg



【本文地址】

公司简介

联系我们

今日新闻


点击排行

实验室常用的仪器、试剂和
说到实验室常用到的东西,主要就分为仪器、试剂和耗
不用再找了,全球10大实验
01、赛默飞世尔科技(热电)Thermo Fisher Scientif
三代水柜的量产巅峰T-72坦
作者:寞寒最近,西边闹腾挺大,本来小寞以为忙完这
通风柜跟实验室通风系统有
说到通风柜跟实验室通风,不少人都纠结二者到底是不
集消毒杀菌、烘干收纳为一
厨房是家里细菌较多的地方,潮湿的环境、没有完全密
实验室设备之全钢实验台如
全钢实验台是实验室家具中较为重要的家具之一,很多

推荐新闻


图片新闻

实验室药品柜的特性有哪些
实验室药品柜是实验室家具的重要组成部分之一,主要
小学科学实验中有哪些教学
计算机 计算器 一般 打孔器 打气筒 仪器车 显微镜
实验室各种仪器原理动图讲
1.紫外分光光谱UV分析原理:吸收紫外光能量,引起分
高中化学常见仪器及实验装
1、可加热仪器:2、计量仪器:(1)仪器A的名称:量
微生物操作主要设备和器具
今天盘点一下微生物操作主要设备和器具,别嫌我啰嗦
浅谈通风柜使用基本常识
 众所周知,通风柜功能中最主要的就是排气功能。在

专题文章

    CopyRight 2018-2019 实验室设备网 版权所有 win10的实时保护怎么永久关闭