.NET 中密封类的性能优势

.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() { }
}
方法算术平均值误差方差中位数比率代码大小
NonSealed0.4465 ns0.0276 ns0.0258 ns0.4437 ns1.0018 B
Sealed0.0107 ns0.0160 ns0.0150 ns0.0000 ns0.027 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_NonSealed1.6560 ns0.0223 ns0.0208 ns1.00
Is_Sealed0.1505 ns0.0221 ns0.0207 ns0.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 { }
方法平均值误差方差中位数比率
NonSealed3.420 ns0.0897 ns0.0881 ns1.0044 B
Sealed2.951 ns0.0781 ns0.0802 ns0.8658 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 { }
方法平均值误差方差中位数比率
NonSealed0.0668 ns0.0156 ns0.0138 ns1.0064 B
Sealed0.0307 ns0.0209 ns0.0185 ns0.5035 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 类型:

93b48fef1581ec22f8e10fe8938d6219.png

你也可以通过编辑 .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

阅读原文,查看作者原文

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/289837.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

jQuery-1.9.1源码分析系列(十) 事件系统——事件绑定

事件绑定的方式有很多种。使用了jQuery那么原来那种绑定方式&#xff08;elem.click function(){...})就不推荐了&#xff0c;原因&#xff1f; 最主要的一个原因是elem.click fn这种方式只能绑定一个事件处理&#xff0c;多次绑定的只会保留最后一次绑定的结果。 看一下jQue…

Windows 8系统平台上应用软件安装心得

1.ArcGIS 10.2安装 需要单独安装.NET 3.5,GIS软件自带的.NET系统不识别&#xff0c;点击360云盘地址进行下载&#xff08;提取码为&#xff1a;1ed3&#xff09;。&#xff08;另外&#xff0c;Win8系统上安装.NET可以参考&#xff1a;http://blog.csdn.net/aijavaer/article/d…

Android视图绘制流程完全解析,带你一步步深入了解View(二)

转自&#xff1a;http://blog.csdn.net/guolin_blog/article/details/16330267 在上一篇文章中&#xff0c;我带着大家一起剖析了一下LayoutInflater的工作原理&#xff0c;可以算是对View进行深入了解的第一步吧。那么本篇文章中&#xff0c;我们将继续对View进行深入探究&…

C# 线程问题之死锁

过多的锁定也会有麻烦。在死锁中&#xff0c;至少有两个线程被挂起&#xff0c;并等待对方解除锁定。由于两个线程都在等待对方&#xff0c;就出现了死锁&#xff0c;线程将无限等待下去。为了说明死锁&#xff0c;下面实例化 StateObject 类型的两个对象&#xff0c;并把它们传…

Matlab图形绘制

1.正余弦曲线 例如自变量从0到10&#xff0c;间隔为0.1的曲线代码如下&#xff1a; 正弦 t 0:.1:10; y sin(t); plot(t,y); 余弦 t 0:.1:10; y cos(t); plot(t,y); 正余弦图形显示如下&#xff1a;

Blazor University (5)组件 — 字面量、表达式和指令

原文链接&#xff1a;https://blazor-university.com/components/literals-expressions-and-directives/字面量、表达式和指令源代码[1]请注意&#xff0c;本节一般不涵盖 Razor 标记。它不会涵盖诸如条件输出、循环等内容。该主题在网络和书籍中的其他地方得到了广泛的介绍。使…

.NET6之MiniAPI(二十七):Metrics

应用的各种Metrics是保证应用健康稳定运行的基础&#xff0c;特别对于一些可用性有所要求的应用&#xff0c;本文介绍prometheus-net这个三方指示库。prometheus-net的工作原理是&#xff0c;在应用内部埋点&#xff0c;通过prometheus采集数据&#xff0c;然后通过grafana把采…

回溯算法之布罗夫卫队(最大团问题)

1、问题 在原始部落中,由于食物缺乏,部落居民经常因为争夺猎物发生冲突,几乎每个居民都 有自己的仇敌。部落酋长为了组织一支保卫部落的卫队,希望从居民中选出最多的居民加入 卫队,并保证卫队中任何两个人都不是仇敌。假设已给定部落中居民间的仇敌关系图,编程 计算构建部落护…

microdot - 一个开源 .NET 微服务框架。

简介Microdot 是一个开源 .NET 框架&#xff0c;可满足轻松创建微服务的许多需求。它的一些主要特点•用于托管微服务的服务容器•服务间 RPC&#xff0c;便于基于接口的服务通信•服务之间的客户端透明响应缓存•日志记录和分布式跟踪支持•客户端负载均衡和服务发现•详细的健…

slider控件控制文本框字体大小

1.控件代码 <pre name"code" class"csharp"> <Slider x:Name"slider1" HorizontalAlignment"Left" Margin"0,261,0,0" VerticalAlignment"Top" Width"446" ValueChanged"Slid…

争时金融java_Java高并发编程基础之AQS

引言曾经有一道比较比较经典的面试题“你能够说说java的并发包下面有哪些常见的类&#xff1f;”大多数人应该都可以说出CountDownLatch、CyclicBarrier、Sempahore多线程并发三大利器。这三大利器都是通过AbstractQueuedSynchronizer抽象类(下面简写AQS)来实现的&#xff0c;所…

Xamarin效果第十三篇之弹窗PopupPage

在上一篇文章中使用Xamarin实现控制了一下祖传的PLC;基本的功能也就完事了,这不总觉得少点最基本的配置;那就趁着激情还在赶紧再去完善一下,不然激情已过就懒得去摸索了;来看看最终咱实现的视频效果:1、关于弹窗,直接使用开源的PopupPageInstall-Package Rg.Plugins.Popup -Ver…

C# 线程问题之争用条件

用多个线程编程并不容易。在启动访问相同数据的多个线程时&#xff0c;会间歇性地遇到难以发现的问题。如果使用任务、并行 LINQ 或 Parallel 类&#xff0c;也会遇到这些问题。为了避免这些问题&#xff0c;必须特别注意同步问题和多个线程可能发生的其他问题。下面探讨与线程…

尾调用优化 java_为什么JVM仍然不支持尾调用优化?

拉丁的传说也许您已经知道这一点&#xff0c;但是这个功能并不像听起来那么简单&#xff0c;因为Java语言实际上将堆栈跟踪暴露给程序员。考虑以下程序&#xff1a;public class Test {public static String f() {String s Math.random() > .5 ? f() : g();return s;}publ…

【AngularJS】—— 2 初识AngularJs(续)

前一篇了解了AngularJS的一些简单的使用&#xff0c;这里继续跟着w3c学习一下剩下的内容。 本篇根据w3cschool.cc继续学习AngularJS剩余的内容&#xff0c;包括&#xff1a; 1 事件 2 模块 3 表单 4 数据验证 5 bootstrap CSS风格 6 include包含其他页面 7 应用程序 8 参考手册…

特斯拉为何使用.NET 技术栈?

【精选转载】| 来源/知乎在知乎上有一个帖子非常热闹&#xff1a;“为何特使拉使用.net core技术栈 而不用 java&#xff1f;”1回答1&#xff1a;Kasim作者&#xff1a;Kasim链接&#xff1a;https://www.zhihu.com/question/496204534/answer/2269157872这题我熟啊&#xff0…

Blazor University (6)组件 — 组件事件

原文链接&#xff1a;https://blazor-university.com/components/component-events/组件事件源代码[1]EventCallback<T> 类是一个特殊的 Blazor 类&#xff0c;可以作为参数公开&#xff0c;以便组件可以在发生感兴趣的事情时轻松通知使用者。一旦声明了 EventCallback&l…

Xamarin效果第十四篇之玩耍GIS

最近再次拾起Xamarin然后也实现了祖传PLC控制和弹窗配置;这不又一次勾起来我想基于他玩玩原来一直玩耍的GIS,毕竟咱前面一直玩耍二维和三维的GIS相关的知识点;有兴趣的小伙伴可以翻翻我的历史文章;趁着激情满满;来看看最终咱实现的加载高德平面地图效果(有水印):再者就是满足群…

PHP进程退出信号_一文吃透 PHP 进程信号处理

背景前两周老大给安排了一个任务&#xff0c;写一个监听信号的包。因为我司的项目是运行在容器里边的&#xff0c;每次上线&#xff0c;需要重新打包镜像&#xff0c;然后启动。在重新打包之前&#xff0c;Dokcer会先给容器发送一个信号&#xff0c;然后等待一段超时时间(默认1…

GitHub Copilot 现已登陆 Visual Studio!

激动人心的好消息来了&#xff0c;GitHub 在3月29日发布博客&#xff0c;宣布 Github Copilot 现在可以在 Visual Studio 中使用。我们知道 Visual Studio 的 IntelliCode 本身已经很智能了, 现在又迎来了 Copilot, 编程体验将进入新的篇章。如何安装? 首先&#xff0c;您…