.NET8极致性能优化Reflection
前言
反射一直是性能的瓶颈,所以无论哪个.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.0 | 1,287.1 ns | 1.00 | 296 B | 1.00 |
GetCustomAttributes | .NET 8.0 | 994.0 ns | 0.77 | 232 B | 0.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<int>);
public Type GetGenericTypeDefinition() => _type.GetGenericTypeDefinition();
}
.NET7和.NET8如下
方法 | 运行时 | 平均值 | 比 |
---|---|---|---|
GetGenericTypeDefinition | .NET 7.0 | 47.426 ns | 1.00 |
GetGenericTypeDefinition | .NET 8.0 | 3.289 ns | 0.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.0 | 91.457 ns | 1.00 |
Method0 | .NET 7.0 | 7.205 ns | 0.08 |
Method0 | .NET 8.0 | 5.719 ns | 0.06 |
Method1 | .NET 6.0 | 132.832 ns | 1.00 |
Method1 | .NET 7.0 | 26.151 ns | 0.20 |
Method1 | .NET 8.0 | 21.602 ns | 0.16 |
Method2 | .NET 6.0 | 172.224 ns | 1.00 |
Method2 | .NET 7.0 | 37.937 ns | 0.22 |
Method2 | .NET 8.0 | 26.951 ns | 0.16 |
Method3 | .NET 6.0 | 211.247 ns | 1.00 |
Method3 | .NET 7.0 | 42.988 ns | 0.20 |
Method3 | .NET 8.0 | 34.112 ns | 0.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的情况如下
方法 | 平均值 | 比率 |
---|---|---|
MethodBaseInvoke | 32.42 ns | 1.00 |
MethodInvokerInvoke | 11.47 ns | 0.35 |
这些类型被 Microsoft.Extensions.DependencyInjection.Abstractions 中的 ActivatorUtilities.CreateFactory 方法使用,以进一步提高 DI 服务构建性能。通过添加额外的缓存层进一步改进,进一步避免每次构建时的反射。
作者:jianghupt
欢迎关注公众号 (jianghupt),文章首发地。
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
铠侠向 Linux 基金会捐赠 Software-Enabled Flash SDK
几年前从东芝分离出来的存储公司 Kioxia(铠侠)向 Linux 基金会捐赠了一个软件开发工具包 (SDK),用于建立 Software-Enabled Flash SDK。 Linux 基金会发布公告称,“SEF SDK 的发布是存储技术领域的一个重要里程碑......SEF 项目对 KIOXIA 突破性地捐赠软件定义闪存原生 SDK 表示热烈欢迎,这将为开发人员提供前所未有的能力,使他们能够为闪存存储(flash storage)应用开发定制的独特软件。” 该 SEF SDK 包括示例代码和文档,以充分利用 flash media control 的潜力;包括 WAF 减少、延迟控制、对 ZNS 和 FDP 或 Block 等多种协议的支持等。 SEF 项目旨在通过加强对驱动器的管理、增强工作负载隔离、加强延迟控制以及实现对闪存管理的更多host-control,在现代数据中心中开辟新的用途并最大限度地发挥基于闪存的存储潜力。
- 下一篇
深度解读 Cascades 查询优化器
数据库中查询优化器是数据库的核心组件,其决定着 SQL 查询的性能。Cascades 优化器是 Goetz 在 volcano optimizer generator 的基础上优化之后诞生的一个搜索框架。 本期技术贴将带大家了解 Cascades 查询优化器。首先介绍 SQL 查询优化器,接着分析查询优化基本原理,最后对 Cascades 查询优化器进行重点介绍。 一、SQL 查询优化器 用户与数据库交互时只需要输入声明式 SQL 语句,数据库优化器则负责将用户输入的 SQL 语句进行各种规则优化,生成最优的执行计划,并交由执行器执行。优化器对于 SQL 查询具有十分重要的意义。 如图 1 所示,SQL 语句经过语法和词法解析生成抽象语法树(AST),经过**基于规则的查询优化(Rule-Based Optimizer)和基于代价的查询优化(Cost-Based Optimizer)**生成可执行计划。 图 1 基于规则的优化算法:基于规则的优化方法的要点在于结构匹配和替换。应用规则的算法一般需要先在关系代数结构上匹配一部分局部的结构,再根据结构的特点进行变换乃至替换操作。 基于成本的...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- MySQL8.0.19开启GTID主从同步CentOS8
- SpringBoot2全家桶,快速入门学习开发网站教程
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- CentOS8编译安装MySQL8.0.19
- CentOS7,8上快速安装Gitea,搭建Git服务器
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- SpringBoot2配置默认Tomcat设置,开启更多高级功能