.NET 中密封类的性能优势

Intro

最近看到一篇文章 Performance benefits of sealed class in .NET,觉得写得不错,翻译一下,分享给大家。

目前看到的一些类库中其实很多并没有考虑使用密封类,如果你的类型是不希望被继承的,或者不需要被重写的,那么就应该考虑声明为密封类,尤其是对于类库项目的作者来说,这其实是非常值得考虑的一件事情,很多优秀的类库都会考虑这样的问题,尤其是 .NET 框架里的一些代码,大家看开源项目源码的时候也可以留意一下。

Preface

默认情况下,类是不密封的。这意味着你可以从它们那里继承。我认为这并不是正确的默认行为。事实上,除非一个类被设计成可以继承,否则它应该被密封。如果有需要,你仍然可以在以后删除 sealed 修饰符。除了不是最好的默认值之外,它还会影响性能。

事实上,当一个类被密封时,JIT可以进行一些优化,并稍微提升应用程序的性能。

在 .NET 7 中应该会有一个新的分析器来检测可以被密封的类。在这篇文章中,我将展示这个 issue https://github.com/dotnet/runtime/issues/49944 中提到的密封类的一些性能优势。

性能优势

虚方法调用

当调用虚方法时,实际的方法是在运行时根据对象的实际类型找到的。每个类型都有一个虚拟方法表(vtable),其中包含所有虚拟方法的地址。这些指针在运行时被用来调用适当的方法实现(动态执行)。

如果JIT知道对象的实际类型,它可以跳过vtable,直接调用正确的方法以提高性能。使用密封类型有助于JIT,因为它知道不能有任何派生类。

public class SealedBenchmark
{readonly NonSealedType nonSealedType = new();readonly SealedType sealedType = new();[Benchmark(Baseline = true)]public void NonSealed(){// The JIT cannot know the actual type of nonSealedType. Indeed,// it could have been set to a derived class by another method.// So, it must use a virtual call to be safe.nonSealedType.Method();}[Benchmark]public void Sealed(){// The JIT is sure sealedType is a SealedType. As the class is sealed,// it cannot be an instance from a derived type.// So it can use a direct call which is faster.sealedType.Method();}
}internal class BaseType
{public virtual void Method() { }
}
internal class NonSealedType : BaseType
{public override void Method() { }
}
internal sealed class SealedType : BaseType
{public override void Method() { }
}
方法 算术平均值 误差 方差 中位数 比率 代码大小
NonSealed 0.4465 ns 0.0276 ns 0.0258 ns 0.4437 ns 1.00 18 B
Sealed 0.0107 ns 0.0160 ns 0.0150 ns 0.0000 ns 0.02 7 B

请注意,当 JIT 可以确定实际类型时,即使类型没有密封,它也可以使用直接调用。例如,以下两个片段之间没有区别:

void NonSealed()
{var instance = new NonSealedType();instance.Method(); // The JIT knows `instance` is NonSealedType because it is set// in the method and never modified, so it uses a direct call
}void Sealed()
{var instance = new SealedType();instance.Method(); // The JIT knows instance is SealedType, so it uses a direct call
}

对象类型转换 (is / as)

当对象类型转换时,CLR 必须在运行时检查对象的类型。当转换到一个非密封的类型时,运行时必须检查层次结构中的所有类型。然而,当转换到一个密封的类型时,运行时必须只检查对象的类型,所以它的速度更快。

public class SealedBenchmark
{readonly BaseType baseType = new();[Benchmark(Baseline = true)]public bool Is_Sealed() => baseType is SealedType;[Benchmark]public bool Is_NonSealed() => baseType is NonSealedType;
}internal class BaseType {}
internal class NonSealedType : BaseType {}
internal sealed class SealedType : BaseType {}
方法 平均值 误差 方差 中位数
Is_NonSealed 1.6560 ns 0.0223 ns 0.0208 ns 1.00
Is_Sealed 0.1505 ns 0.0221 ns 0.0207 ns 0.09

数组 Arrays

.NET中的数组是支持协变的。这意味着,BaseType[] value = new DerivedType[1] 是有效的。而其他集合则不是这样的。例如,List<BaseType> value = new List<DerivedType>(); 是无效的。

协变会带来性能上的损失。事实上,JIT在将一个项目分配到数组之前必须检查对象的类型。当使用密封类型时,JIT可以取消检查。你可以查看 Jon Skeet 的文章 https://codeblog.jonskeet.uk/2013/06/22/array-covariance-not-just-ugly-but-slow-too/ 来获得更多关于性能损失的细节。

public class SealedBenchmark
{SealedType[] sealedTypeArray = new SealedType[100];NonSealedType[] nonSealedTypeArray = new NonSealedType[100];[Benchmark(Baseline = true)]public void NonSealed(){nonSealedTypeArray[0] = new NonSealedType();}[Benchmark]public void Sealed(){sealedTypeArray[0] = new SealedType();}}internal class BaseType { }
internal class NonSealedType : BaseType { }
internal sealed class SealedType : BaseType { }
方法 平均值 误差 方差 中位数 比率
NonSealed 3.420 ns 0.0897 ns 0.0881 ns 1.00 44 B
Sealed 2.951 ns 0.0781 ns 0.0802 ns 0.86 58 B

数组转换成 Span

你可以将数组转换为 Span<T>ReadOnlySpan<T>。出于与前面部分相同的原因,JIT在将数组转换为 Span<T> 之前必须检查对象的类型。当使用一个密封的类型时,可以避免检查并稍微提高性能。

public class SealedBenchmark
{SealedType[] sealedTypeArray = new SealedType[100];NonSealedType[] nonSealedTypeArray = new NonSealedType[100];[Benchmark(Baseline = true)]public Span<NonSealedType> NonSealed() => nonSealedTypeArray;[Benchmark]public Span<SealedType> Sealed() => sealedTypeArray;
}public class BaseType {}
public class NonSealedType : BaseType { }
public sealed class SealedType : BaseType { }
方法 平均值 误差 方差 中位数 比率
NonSealed 0.0668 ns 0.0156 ns 0.0138 ns 1.00 64 B
Sealed 0.0307 ns 0.0209 ns 0.0185 ns 0.50 35 B

检测不可达的代码

当使用密封类型时,编译器知道一些转换是无效的。所以,它可以报告警告和错误。这可能会减少你的应用程序中的错误,同时也会删除不可到达的代码。

class Sample
{public void Foo(NonSealedType obj){_ = obj as IMyInterface; // ok because a derived class can implement the interface}public void Foo(SealedType obj){_ = obj is IMyInterface; // ⚠️ Warning CS0184_ = obj as IMyInterface; // ❌ Error CS0039}
}public class NonSealedType { }
public sealed class SealedType { }
public interface IMyInterface { }

寻找可以被密封的类型

Meziantou.Analyzer 包含一个规则,可以检查可能被密封的类型。

dotnet add package Meziantou.Analyzer

它应该使用  MA0053 报告任何可以被密封的internal 类型:

你也可以通过编辑 .editorconfig文件指示分析器报告 public类型。

[*.cs]
dotnet_diagnostic.MA0053.severity = suggestion# Report public classes without inheritors (default: false)
MA0053.public_class_should_be_sealed = true# Report class without inheritors even if there is virtual members (default: false)
MA0053.class_with_virtual_member_shoud_be_sealed = true

你可以使用像 dotnet format 这样的工具来解决这个问题。

dotnet format analyzers --severity info

注意:在.NET 7中,这应该是 CA1851 的标准静态分析的一部分 https://github.com/dotnet/roslyn-analyzers/pull/5594

补充说明

所有的基准都是使用以下配置运行的:

BenchmarkDotNet=v0.13.1, OS=Windows 10.0.22000
AMD Ryzen 7 5800X, 1 CPU, 16 logical and 8 physical cores
.NET SDK=7.0.100-preview.2.22153.17[Host]     : .NET 6.0.3 (6.0.322.12309), X64 RyuJITDefaultJob : .NET 6.0.3 (6.0.322.12309), X64 RyuJIT

其他资源

  • Why Are So Many Of The Framework Classes Sealed?

  • Analyzer Proposal: Seal internal/private types

More

从上面的解释和基准测试中我们可以看到一些密封类为我们带来的好处,我们在设计一个类型的时候就应该去考虑这个类型是不是允许被继承,如果不允许被继承,则应该考虑将其声明为 sealed,如果你有尝试过 Sonar Cloud 这样的静态代码分析工具,你也会发现,有一些 private 的类型如果没有声明为 sealed 就会被报告为 Code Smell 一个代码中的坏味道

除了性能上的好处,首先将一个类型声明为 sealed 可以实现更好的 API 兼容性,如果从密封类变成一个非密封类不是一个破坏性的变更,但是从一个非密封类变成一个密封类是一个破坏性的变更

希望大家在自己的类库项目中新建类型的时候会思考一下是否该将其声明为 sealed,除此之外可以不 public 的类型可以声明为 internal,不 public 不必要的类型,希望有越来越多更好更高质量的开源项目

原文地址:https://www.meziantou.net/performance-benefits-of-sealed-class.htm

阅读原文,查看作者原文

.NET 中密封类的性能优势相关推荐

  1. nocount on_在SQL Server中设置NOCOUNT ON语句的用法和性能优势

    nocount on Have you ever noticed SET NOCOUNT ON statement in T-SQL statements or stored procedures i ...

  2. 服务器电源管理系统SPM 价格,Liebert SPM 2.0服务器电源管理系统:在对比中彰显技术特性和性能优势...

    原标题:Liebert SPM 2.0服务器电源管理系统:在对比中彰显技术特性和性能优势 集机房配电.隔离.接地.监测.管理于一体; 通过界面显示既可及时了解繁杂IT负载的供电系统.主电源.断路器和分 ...

  3. 使用原型相对于直接在构造函数中定义方法的优势? [重复]

    本文翻译自:Advantages of using prototype, vs defining methods straight in the constructor? [duplicate] Th ...

  4. WCF性能优势体现 【转】

    WCF性能优势决定了其受欢迎程度,这些优势主要都体现在:统一性:互操作性:安全与可信赖:兼容性等方面. WCF是使用托管代码建立和运行面向服务(Service Oriented)应用程序的统一框架. ...

  5. PE钢丝网骨架管的性能优势

    PE钢丝网骨架管的性能优势 PE钢丝网骨架管是一种长距离埋地用供水.输气管道,由于适用条件的特殊性,也对此管材的性能提出一定的要求,既然它在这些情况下还是能被广泛的应用. 1.耐冲击性 钢丝网骨架复合 ...

  6. 多变量变送器工作原理及独特的性能优势简述

    多变量变送器工作原理及独特的性能优势简述 多变量变送器的量程原理及技术就是测量带有同时工作的多支传感器装置,并以现场总线Hart通讯协议输出多个测量结果.同时具备温度变送及压力变送功能,一般用于流量的 ...

  7. 郎凤娥谈山西蓝天煤粉工业锅炉的技术及性能优势

    山西蓝天煤粉工业锅炉有哪些技术和性能优势?据郎凤娥介绍:山西蓝天环保生产的新型高效节能环保煤粉锅炉具有自主知识产权,经北京节能环保中心.太原理工大学节能技术研究所测试审核,该技术产品具有十分显著的节能 ...

  8. 干货干货FPGA lattice深力科 FPGA性能优势以及市场前景分析 以及lattice MachXO2系列MachXO3系列资料参考

    干货干货FPGA lattice深力科 FPGA性能优势以及市场前景分析 以及lattice MachXO2系列MachXO3系列资料参考 那什么是FPGA芯片呢? FPGA全称为:现场可编程逻辑门阵 ...

  9. win10php测试,window_Win10对决Win8:测试表明两者相比没有性能优势,目前,要搞清楚Windows 10性能相 - phpStudy...

    Win10对决Win8:测试表明两者相比没有性能优势 目前,要搞清楚Windows 10性能相当困难.要对这款计划于7月29日发布的操作系统进行测试并非易事,因为公众还不能安装RTM版本.微软一直没有 ...

最新文章

  1. python循环语句-python中的for循环语句怎么写
  2. contains不区分大小写_趣读丨2020祝福语怎么发才不像是群发?全网独一份的模板安排上了!...
  3. 新闻添加html页面
  4. Linux shutdown指令
  5. 商战特训营 竞合赛场显英才
  6. data model of Organization unit - Sales office
  7. 51信用卡微服务集成测试自动化探索
  8. vue 中获取select 的option的value 直接click?
  9. 搜狗浏览器连接海康摄像头,无法显示画面
  10. 莫烦PYTHON | Tensorflow教程——Tensorflow简介(第一章)
  11. Android MTK CAMERA DRIVER 摄像头驱动
  12. dayjs格式化使用
  13. Error: Can't resolve 'fs' in (webpack 2配置CSS loadeers)
  14. lambda表达式与正则表达式
  15. 选择RAKsmart服务器要考虑什么因素
  16. 多库多表场景下使用 Amazon EMR CDC 实时入湖最佳实践
  17. SpringBoot+Vue项目社团网站
  18. VS2019设置easyx图形库
  19. Markdown 基础操作 | 高级操作 大全+Typora常用快捷键
  20. flex 的 三个参数:flex-grow、flex-shrink、flex-basis

热门文章

  1. 第16讲 用户程序的结构与执行
  2. nginx的负载均衡集群
  3. 基于事件的 NIO 多线程服务器
  4. Wordpress 提速之 Gzip 压缩
  5. [FW]软件开发中的11个系统思维定律
  6. linux tomcat 启动权限不足解决办法
  7. php strtoup,PHP 7 的几处函数安全小变化
  8. day22 模块-collections,time,random,pickle,shelve等
  9. vim cheat-sheet
  10. 主动给团队或用户安装Teams App