自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

.NET 中密封類的性能優(yōu)勢(shì),你知道幾個(gè)?

開(kāi)發(fā) 前端
如果JIT知道對(duì)象的實(shí)際類型,它可以跳過(guò)vtable,直接調(diào)用正確的方法以提高性能。使用密封類型有助于JIT,因?yàn)樗啦荒苡腥魏闻缮悺?/div>

Intro

最近看到一篇文章 Performance benefits of sealed class in .NET,覺(jué)得寫(xiě)得不錯(cuò),翻譯一下,分享給大家。

目前看到的一些類庫(kù)中其實(shí)很多并沒(méi)有考慮使用密封類,如果你的類型是不希望被繼承的,或者不需要被重寫(xiě)的,那么就應(yīng)該考慮聲明為密封類,尤其是對(duì)于類庫(kù)項(xiàng)目的作者來(lái)說(shuō),這其實(shí)是非常值得考慮的一件事情,很多優(yōu)秀的類庫(kù)都會(huì)考慮這樣的問(wèn)題,尤其是 .NET 框架里的一些代碼,大家看開(kāi)源項(xiàng)目源碼的時(shí)候也可以留意一下。

Preface

默認(rèn)情況下,類是不密封的。這意味著你可以從它們那里繼承。我認(rèn)為這并不是正確的默認(rèn)行為。事實(shí)上,除非一個(gè)類被設(shè)計(jì)成可以繼承,否則它應(yīng)該被密封。如果有需要,你仍然可以在以后刪除 sealed 修飾符。除了不是最好的默認(rèn)值之外,它還會(huì)影響性能。

事實(shí)上,當(dāng)一個(gè)類被密封時(shí),JIT可以進(jìn)行一些優(yōu)化,并稍微提升應(yīng)用程序的性能。

在 .NET 7 中應(yīng)該會(huì)有一個(gè)新的分析器來(lái)檢測(cè)可以被密封的類。在這篇文章中,我將展示這個(gè) issue https://github.com/dotnet/runtime/issues/49944 中提到的密封類的一些性能優(yōu)勢(shì)。

性能優(yōu)勢(shì)

虛方法調(diào)用

當(dāng)調(diào)用虛方法時(shí),實(shí)際的方法是在運(yùn)行時(shí)根據(jù)對(duì)象的實(shí)際類型找到的。每個(gè)類型都有一個(gè)虛擬方法表(vtable),其中包含所有虛擬方法的地址。這些指針在運(yùn)行時(shí)被用來(lái)調(diào)用適當(dāng)?shù)姆椒▽?shí)現(xiàn)(動(dòng)態(tài)執(zhí)行)。

如果JIT知道對(duì)象的實(shí)際類型,它可以跳過(guò)vtable,直接調(diào)用正確的方法以提高性能。使用密封類型有助于JIT,因?yàn)樗啦荒苡腥魏闻缮悺?/p>

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() { }
}

方法

算術(shù)平均值

誤差

方差

中位數(shù)

比率

代碼大小

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

請(qǐng)注意,當(dāng) JIT 可以確定實(shí)際類型時(shí),即使類型沒(méi)有密封,它也可以使用直接調(diào)用。例如,以下兩個(gè)片段之間沒(méi)有區(qū)別:

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
}

對(duì)象類型轉(zhuǎn)換 (is / as)

當(dāng)對(duì)象類型轉(zhuǎn)換時(shí),CLR 必須在運(yùn)行時(shí)檢查對(duì)象的類型。當(dāng)轉(zhuǎn)換到一個(gè)非密封的類型時(shí),運(yùn)行時(shí)必須檢查層次結(jié)構(gòu)中的所有類型。然而,當(dāng)轉(zhuǎn)換到一個(gè)密封的類型時(shí),運(yùn)行時(shí)必須只檢查對(duì)象的類型,所以它的速度更快。

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 {}

方法

平均值

誤差

方差

中位數(shù)

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

數(shù)組 Arrays

.NET中的數(shù)組是支持協(xié)變的。這意味著,BaseType[] value = new DerivedType[1] 是有效的。而其他集合則不是這樣的。例如,List value = new List(); 是無(wú)效的。

協(xié)變會(huì)帶來(lái)性能上的損失。事實(shí)上,JIT在將一個(gè)項(xiàng)目分配到數(shù)組之前必須檢查對(duì)象的類型。當(dāng)使用密封類型時(shí),JIT可以取消檢查。你可以查看 Jon Skeet 的文章 https://codeblog.jonskeet.uk/2013/06/22/array-covariance-not-just-ugly-but-slow-too/ 來(lái)獲得更多關(guān)于性能損失的細(xì)節(jié)。

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 { }
方法平均值誤差方

方法

平均值

誤差

方差

中位數(shù)

比率

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

數(shù)組轉(zhuǎn)換成 Span

你可以將數(shù)組轉(zhuǎn)換為 Span 或 ReadOnlySpan。出于與前面部分相同的原因,JIT在將數(shù)組轉(zhuǎn)換為 Span 之前必須檢查對(duì)象的類型。當(dāng)使用一個(gè)密封的類型時(shí),可以避免檢查并稍微提高性能。

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 { }

方法

平均值

誤差

方差

中位數(shù)

比率

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

檢測(cè)不可達(dá)的代碼

當(dāng)使用密封類型時(shí),編譯器知道一些轉(zhuǎn)換是無(wú)效的。所以,它可以報(bào)告警告和錯(cuò)誤。這可能會(huì)減少你的應(yīng)用程序中的錯(cuò)誤,同時(shí)也會(huì)刪除不可到達(dá)的代碼。

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 包含一個(gè)規(guī)則,可以檢查可能被密封的類型。

dotnet add package Meziantou.Analyzer

它應(yīng)該使用 MA0053 報(bào)告任何可以被密封的internal 類型:

你也可以通過(guò)編輯 .editorconfig文件指示分析器報(bào)告 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 這樣的工具來(lái)解決這個(gè)問(wèn)題。

dotnet format analyzers --severity info

注意:在.NET 7中,這應(yīng)該是 CA1851 的標(biāo)準(zhǔn)靜態(tài)分析的一部分 https://github.com/dotnet/roslyn-analyzers/pull/5594

補(bǔ)充說(shuō)明

所有的基準(zhǔn)都是使用以下配置運(yùn)行的:

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 RyuJIT
DefaultJob : .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

從上面的解釋和基準(zhǔn)測(cè)試中我們可以看到一些密封類為我們帶來(lái)的好處,我們?cè)谠O(shè)計(jì)一個(gè)類型的時(shí)候就應(yīng)該去考慮這個(gè)類型是不是允許被繼承,如果不允許被繼承,則應(yīng)該考慮將其聲明為 sealed,如果你有嘗試過(guò) Sonar Cloud 這樣的靜態(tài)代碼分析工具,你也會(huì)發(fā)現(xiàn),有一些 private 的類型如果沒(méi)有聲明為 sealed 就會(huì)被報(bào)告為 Code Smell 一個(gè)代碼中的壞味道

除了性能上的好處,首先將一個(gè)類型聲明為 sealed 可以實(shí)現(xiàn)更好的 API 兼容性,如果從密封類變成一個(gè)非密封類不是一個(gè)破壞性的變更,但是從一個(gè)非密封類變成一個(gè)密封類是一個(gè)破壞性的變更

希望大家在自己的類庫(kù)項(xiàng)目中新建類型的時(shí)候會(huì)思考一下是否該將其聲明為 sealed,除此之外可以不 public 的類型可以聲明為 internal,不 public 不必要的類型,希望有越來(lái)越多更好更高質(zhì)量的開(kāi)源項(xiàng)目

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

責(zé)任編輯:武曉燕 來(lái)源: amazingdotnet
相關(guān)推薦

2023-11-07 07:16:14

云計(jì)算AWS谷歌

2019-05-10 11:13:19

分析工具Java

2022-04-13 10:05:48

網(wǎng)關(guān)Flowable事件訂閱

2024-05-09 10:28:46

容器ListElement?

2024-08-06 11:40:57

2024-02-19 08:07:31

Go版本語(yǔ)言

2025-02-26 00:56:24

.Net開(kāi)源項(xiàng)目

2025-03-25 10:49:13

2024-10-28 13:18:54

2024-03-01 13:48:00

Git配置系統(tǒng)

2022-02-15 07:26:31

Ncat工具Linux

2021-11-04 11:54:30

Linux內(nèi)存系統(tǒng)

2021-06-01 05:16:49

前端開(kāi)發(fā)技術(shù)熱點(diǎn)

2021-08-10 08:01:08

Synchronize鎖膨脹鎖消除

2023-09-18 08:56:57

StringJava

2021-11-01 23:55:09

Java接口密封類

2021-02-27 17:13:21

前端代碼邏輯

2021-10-12 09:20:02

數(shù)據(jù)庫(kù)SQL腳本

2024-01-18 00:16:07

2022-01-10 11:33:17

Go測(cè)試軟件
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)