做外贸要看哪些网站,电商排名前十名品牌,大型网站开发框架移动前端框架,vps网站无法通过ip访问.NET 中密封类的性能优势Intro最近看到一篇文章 Performance benefits of sealed class in .NET#xff0c;觉得写得不错#xff0c;翻译一下#xff0c;分享给大家。目前看到的一些类库中其实很多并没有考虑使用密封类#xff0c;如果你的类型是不希望被继承的#xff0c… .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 BSealed0.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.00Is_Sealed0.1505 ns0.0221 ns0.0207 ns0.09数组 Arrays.NET中的数组是支持协变的。这意味着BaseType[] value new DerivedType[1] 是有效的。而其他集合则不是这样的。例如ListBaseType value new ListDerivedType(); 是无效的。协变会带来性能上的损失。事实上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 BSealed2.951 ns0.0781 ns0.0802 ns0.8658 B数组转换成 Span你可以将数组转换为 SpanT 或 ReadOnlySpanT。出于与前面部分相同的原因JIT在将数组转换为 SpanT 之前必须检查对象的类型。当使用一个密封的类型时可以避免检查并稍微提高性能。public class SealedBenchmark
{SealedType[] sealedTypeArray new SealedType[100];NonSealedType[] nonSealedTypeArray new NonSealedType[100];[Benchmark(Baseline true)]public SpanNonSealedType NonSealed() nonSealedTypeArray;[Benchmark]public SpanSealedType Sealed() sealedTypeArray;
}public class BaseType {}
public class NonSealedType : BaseType { }
public sealed class SealedType : BaseType { }方法平均值误差方差中位数比率NonSealed0.0668 ns0.0156 ns0.0138 ns1.0064 BSealed0.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 类型:你也可以通过编辑 .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补充说明所有的基准都是使用以下配置运行的BenchmarkDotNetv0.13.1, OSWindows 10.0.22000
AMD Ryzen 7 5800X, 1 CPU, 16 logical and 8 physical cores
.NET SDK7.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 typesMore从上面的解释和基准测试中我们可以看到一些密封类为我们带来的好处我们在设计一个类型的时候就应该去考虑这个类型是不是允许被继承如果不允许被继承则应该考虑将其声明为 sealed如果你有尝试过 Sonar Cloud 这样的静态代码分析工具你也会发现有一些 private 的类型如果没有声明为 sealed 就会被报告为 Code Smell 一个代码中的坏味道除了性能上的好处首先将一个类型声明为 sealed 可以实现更好的 API 兼容性如果从密封类变成一个非密封类不是一个破坏性的变更但是从一个非密封类变成一个密封类是一个破坏性的变更希望大家在自己的类库项目中新建类型的时候会思考一下是否该将其声明为 sealed除此之外可以不 public 的类型可以声明为 internal不 public 不必要的类型希望有越来越多更好更高质量的开源项目原文地址https://www.meziantou.net/performance-benefits-of-sealed-class.htm阅读原文查看作者原文