為什么要把類設置成密封?

前幾天筆者提交了關于FasterKvCache的性能優化代碼,其中有一個點就是我把一些后續不需要繼承的類設置為了sealed密封類,然后就有小伙伴在問,為啥這個地方需要設置成sealedad4eae8cee3248f52b8bb891ab45dc73.png

提交的代碼如下所示:5710abcc7d9a67d8fa4ece3075b7ddbe.png

一般業務開發的同學可能接觸密封類比較少,密封類除了框架設計約束(不能被繼承)以外,還有一個微小的性能提升,不過雖然它是一個微小的優化點,多框架開發的作者都會做這樣的優化,如果方法調用的頻次很高,那也會帶來很大的收益。

筆者最開始是從.NET runtime 中的代碼學習到這一個優化技巧,后面有看到meziantou大佬的文章performance-benefits-of-sealed-class[1]完整的學習了一下。

然后本來是想翻譯一下這篇文章,找了下發現 Weihan 大佬今年年初翻譯了meziantou大佬的文章,質量非常高的中文版,大家可以戳鏈接看看,既然如此在本文中帶大家回顧一下文章中例子,另外從 JIT ASM 的層面分析為什么性能會有提升。

性能優勢

虛方法調用

在上面提到的文章例子中,有一個虛方法的調用,大家其實要明白一點,現在面向對象的封裝、繼承、多態中的多態實現主要就是靠虛方法。

一個類型可能會有子類,子類可能會重寫類型的方法從而達到不同的行為(多態),而這些重寫的方法都在虛方法表里,調用的話就需要查表。450fc0d87ec3752d9dff8892c8cfd0b3.png

回到文中的代碼,大佬構建了一個這樣的測試用例:

public?class?SealedBenchmark
{readonly?NonSealedType?nonSealedType?=?new();readonly?SealedType?sealedType?=?new();[Benchmark(Baseline?=?true)]public?void?NonSealed(){//?JIT不能知道nonSealedType的實際類型.//?它可能已經被另一個方法設置為派生類。//?所以,為了安全起見,它必須使用一個虛擬調用。nonSealedType.Method();}[Benchmark]public?void?Sealed(){// JIT確信sealedType是一個SealedType。?由于該類是密封的。//?它不可能是一個派生類型的實例。//?所以它可以使用直接調用,這樣會更快。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()?{?}
}

取得的結果就是密封類要比非密封的快 98%。7f08c1139a4ebcb96f99564ebf9946af.png

那么為什么會這樣呢?首先我們來比較一下兩個方法的 IL 代碼,發現是一模一樣的,對于方法調用都是用了callvirt(它就是用來調用虛方法的,想了解更多詳情可以看這里[2]),因為 instance 是從字段中加載的,編譯器無法知道具體的類型,只能使用callvirta37991137f549eaf625a371fb8cfcf76.png

那區別在哪里呢?我們可以看到 JIT 生成后的匯編代碼,可以很清楚的看到密封類少了兩條指令,因為 JIT 可以從密封類中知道它不可能被繼承,也不可能被重寫,所以是直接跳轉到密封類目標方法執行,而非密封類還有一個查表的過程。而現在很多大佬聊天說 JIT 的"去虛擬化"其實主要就是在 JIT 編譯時去除了callvirt調用。430b1e17ca95fd06cc89fadfff83d416.png

另外文中也提到了一段代碼,如果 JIT 能確定類型,也是直接調用的:

void?NonSealed()
{var?instance?=?new?NonSealedType();instance.Method();?//?JIT知道`instance`是NonSealedType,因為它是在方法中被創建的,//?從未被修改過,所以它使用直接調用
}void?Sealed()
{var?instance?=?new?SealedType();instance.Method();?//?JIT知道類型是SealedType,?所以直接調用
}

此時兩者的匯編代碼沒有任何區別,都是直接 jmp 到目標方法。b0ecd736961877c63881b9389424effb.png

發現一個有趣的東西,如果我們切到.NET Framework 的 JIT,可以發現.NET Framework 的 JIT 沒有.NET 生成的這么高效,沒有直接 jmp 到目標方法,而是多了一層 call 和 ret。所以,朋友們還等什么呢?快升級.NET 版本吧。25c0996f1fee51b98e7044d73f709656.png

對象類型轉換 (is?/?as)

同樣有下面這樣一段代碼,測試密封類和非密封類的對象類型轉換性能:

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

毫無疑問,密封類快 91%。9bb3e251766872d1df892bddd54542d0.png

IL 層面,兩個方法都是一模一樣:318abc731e0ef6178c472f80c73484e3.png

可以看到密封類的代碼相當高效,直接比較一下就轉換類型返回了,而非密封類還需要 call 方法走查表流程:2e87bd414875655f8394298b8dc8d5ac.png

數組

.NET 的數組是協變的,協變兼容的話就意味著在添加進入數組時需要檢查它的類型,而如果是密封類那就可以刪除檢查,同樣有下面一段代碼:

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

密封類的性能要高 14%左右。197904156758006f57c111e0c1a7bed5.png

打開 IL 代碼,兩者編譯出的方法都是一樣的,但是跳轉到匯編代碼可以發現差別,同樣的是Stelem.Ref給數組賦值,密封類只是檢查了一下數組長度,然后直接賦值,而非密封類還需要調用System.Runtime.CompilerServices.CastHelpers.StelemRef進行檢查才能完成賦值。2ea1b8b12a9a905c80815fcb400f1662.png

將數組轉換為Span<T>

和數組一樣,將數組轉換為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?{?}

密封類的性能要高 50%:77029232c132f41da02291de4d4fabfe.png

同樣,這也是 IL 一模一樣的,在 JIT 階段做的優化,可以明顯的看到,JIT 為非密封類單獨做了類型檢查:ac67653e6c0f7544ef7de01db17a66c0.png

總結

筆者在 FasterKvCache 代碼中將一些類設置為sealed的原因顯而易見:

  • 為了讓類的職責更加清晰,在設計中沒有計劃讓它有派生類

  • 為了性能的提升,JIT 優化可以讓其方法調用更快

還有更多有趣的東西(比如 IDE 智能提示將類設置為密封,如何使用 dotnet format 集成這些分析),大家可以翻閱原文或者 Weihan 大佬翻譯的文章。

  • https://www.meziantou.net/performance-benefits-of-sealed-class.htm

  • https://mp.weixin.qq.com/s/dZlEjOB8jx0ku8eN8AhpzQ

.NET性能優化交流群

相信大家在開發中經常會遇到一些性能問題,苦于沒有有效的工具去發現性能瓶頸,或者是發現瓶頸以后不知道該如何優化。于是很高興的在這里宣布,我創建了一個專門交流.NET性能優化經驗的群組,主題包括但不限于:

  • 如何找到.NET性能瓶頸,如使用APM、dotnet tools等工具

  • .NET框架底層原理的實現,如垃圾回收器、JIT等等

  • 如何編寫高性能的.NET代碼,哪些地方存在性能陷阱

希望能有更多志同道合朋友加入,分享一些工作中遇到的.NET性能問題和寶貴的性能分析優化經驗。由于已經達到200人,可以加我微信,我拉你進群: ls1075

c3e57ff0944e043d503cbc53ae18fb08.jpeg

參考資料

[1]

performance-benefits-of-sealed-class: https://www.meziantou.net/performance-benefits-of-sealed-class.htm

[2]

這里: https://learn.microsoft.com/zh-cn/dotnet/api/system.reflection.emit.opcodes.callvirt?view=net-7.0

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/280822.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/280822.shtml
英文地址,請注明出處:http://en.pswp.cn/news/280822.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

powershell 常用命令筆記

常用集合&#xff0c;方便后續復制粘貼 # 判斷文件在不在 # 輸出文件 IF(!(test-path $filePath)) {$result|Out-File $filePath }# 讀取txt $result(Get-Content $filePath -TotalCount 1).Trim() $result# 刪除文件 remove-item "C:\wistron\Datasource\spiderPort.txt…

Linux 性能監控 : CPU 、Memory 、 IO 、Network

一、CPU 1.良好狀態指標 CPU利用率&#xff1a;User Time < 70%&#xff0c;System Time < 35%&#xff0c;User Time System Time < 70% 上下文切換&#xff1a;與CPU利用率相關聯&#xff0c;如果CPU利用率狀態良好&#xff0c;大量的上下文切換也是可以接受的 可…

Java 打飛機(小游戲)[版權非本人 本人制作收藏整理]

今天在網絡上 看到一個純java的小游戲 代碼copy到 myeclipse中 居然效果還不錯 這是一些效果圖 當然了 圖片是我自己找的 有心興趣的朋友可以做的好看一點 具體的代碼 都放在自己的文件里去了 那么可以去下載 https://i.cnblogs.com/Files.aspx 轉載于:https://www.cnblogs…

Cygwin使用指南

1 引言cygwin是一個在windows平臺上運行的unix模擬環境&#xff0c;是cygnus solutions公司開發的自由軟件&#xff08;該公司開發了很多好東西&#xff0c;著名的還有eCos&#xff0c;不過現已被Redhat收購&#xff09;。它對于學習unix/linux操作環境&#xff0c;或者從unix到…

nest 架構_當有人打來您的Nest Hello時,如何讓Google Home通知您

nest 架構The Nest Hello can alert you on your phone whenever someone rings your doorbell, but if you have a Google Home, you can also have Google Assistant audibly announce that someone is at the door. 無論何時有人按下門鈴&#xff0c; Nest Hello都會在電話上…

如何序列化派生類

前言假設有一個 Person 抽象基類&#xff0c;其中包含 Student 和 Teacher 派生類&#xff1a;public class Person {public string Name { get; set; } }public class Student : Person {public int Score { get; set; } }public class Teacher : Person {public string Title…

OPC Client “failed to execute OPCENUM” 解決方法

進入cmd重新執行 OpcEnum.exe /regserver 即可。

django07: 模板語言(舊筆記)

詳見&#xff1a;https://www.cnblogs.com/liwenzhou/p/7931828.html#autoid-2-3-6 包含&#xff1a; 模板 塊 組件 靜態文件

block,inline和inline-block概念和區別

block&#xff1a;block-level elements (塊級元素) &#xff0c;inline&#xff1a; inline elements (內聯元素)。block元素通常被現實為獨立的一塊&#xff0c;會單獨換一行&#xff1b;inline元素則前后不會產生換行&#xff0c;一系列inline元素都在一行內顯示&#xff0c…

Hadoop3.0 WordCount測試一直Accept 狀態,Nodes of the cluster 頁面node列表個數為0

起因是我運行wordcount測試一直卡主&#xff0c;不能執行&#xff0c;一直處于 Accept 狀態&#xff0c;等待被執行&#xff0c;剛開始是各種配置yarn參數&#xff0c;以及host配置&#xff0c;后來發現還是不行 hadoop 集群安裝完成后&#xff0c;在50070的 HDFS 管理后臺能看…

nexus 手動增加_如何使用Google的工廠圖像手動升級Nexus設備

nexus 手動增加Google’s Nexus devices are supposed to receive timely updates, but the staggered rollout means it can take weeks for devices to receive over-the-air (OTA) updates. Luckily, there’s a faster (and geekier) way to install the latest version of…

教你創建Google網站地圖Sitemap.xml(轉)

http://teachmyself.blog.163.com/blog/static/18881422920119895248288/ Sitemap.xml是 google搞出來的&#xff0c;也就是網站地圖&#xff0c;不過這個網站地圖是用xml寫的&#xff0c;而且要按google的標準來寫&#xff0c;并且要將寫出來的這個文件 sitemap.xml上傳到自己…

Oracle存儲過程語法

創建基本的存儲過程 1 CREATE OR REPLACE PROCEDURE MyProName IS 2 BEGIN 3 NULL; 4 END; 行1:CREATE OR REPLACE PROCEDURE 是一個SQL語句通知Oracle數據庫去創建一個叫做skeleton存儲過程, 如果存在就覆蓋它; 行2:IS關鍵詞表明后面將跟隨一個PL/SQL體。 行3:BEGIN關鍵詞表…

WPF-16 圖形處理

我們這節主要介紹WPF常用畫圖標簽&#xff0c;由于WPF圖形處理設計大量篇幅 ,我們在這里拋磚引玉&#xff0c;具體更多的學習資料鏈接https://github.com/microsoft/WPF-Samples/tree/master/Graphics 該鏈接中微軟提供了大量的學習Demo&#xff0c;WPF圖形處理最大的區別在于…

powershell 文件/文件夾操作

新建文件夾 New-Item -ItemType Directory -Force -Path $TargetPath復制文件夾到另外文件夾 Copy-Item <源文件夾> <新文件夾> -recurse -force 復制文件&#xff08;與修改文件名&#xff09; // 達到復制文件到新文件夾&#xff0c;及修改文件名效果 copy-…

純CSS制作各種各樣的網頁圖標(三角形、暫停按鈕、下載箭頭、加號等)

三角形 <div class"box"></div> <style>.box{ width: 0;height: 0;border-top: 50px solid transparent;border-bottom: 50px solid transparent;border-left: 50px solid transparent;border-right: 50px solid red; } </style> 平行四邊形…

您的MyFitnessPal帳戶幾乎肯定已被黑客入侵,請立即更改密碼

If you’re one of the millions of the 150 million MyFitnessPal users, bad news: hackers have your email address, your user name, and your hashed password. 如果您是1.5億MyFitnessPal用戶中的數百萬用戶之一&#xff0c;那么這是個壞消息&#xff1a;黑客擁有您的電…

Oracle Grid 11.2.0.4 安裝是出現INS-30510: Insufficient number of ASM disks selected.

最新文章&#xff1a;Virsons Blog 錯誤的原因是由于磁盤數和冗余層級不匹配&#xff1a; 如果創建用來存放OCR和VOTEDISK的ASM磁盤組&#xff0c;那么External、Normal、High三種冗余級別對應的Failgroup個數是1、3、5。也就是說&#xff0c;創建這三種冗余級別的磁盤組至少分…

動態編譯庫 Natasha 5.0 版本發布

動態編譯庫 Natasha 5.0 于十月份發布&#xff0c;此次大版本更新帶來了強大的兼容性支持&#xff0c;目前 Natasha 已支持 .NET Standard 2.0 及 .NET Core 3.1 以上版本&#xff08;包括 .NET Framework&#xff09;了。引入項目NuGet\Install-Package DotNetCore.Natasha.CS…

著名軟件公司的java筆試算法題!(含參考答案)

原題如下&#xff1a;用1、2、2、3、4、5這六個數字&#xff0c;用java寫一個main函數&#xff0c;打印出所有不同的排列&#xff0c;如&#xff1a;512234、412345等&#xff0c;要求&#xff1a;"4"不能在第三位&#xff0c;"3"與"5"不能相連.…