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

DotNET 5中的gRPC性能改進(jìn),超Golang和C++

開發(fā) 前端
還有一個月,下個月微軟.NET 5將會正式發(fā)布,在大家都關(guān)注新型語言。不知道有對.NET 5有沒有什么期待。

還有一個月,下個月微軟.NET 5將會正式發(fā)布,在大家都關(guān)注新型語言。不知道有對.NET 5有沒有什么期待。

日前官方發(fā)布了一些針對.net 5特性說明的,其中g(shù)RPC性能上的表現(xiàn)令人矚目。在不同gRPC服務(wù)器實現(xiàn)的社區(qū)運行基準(zhǔn)測試中,.NET的QPS超越C++和Go,排在Rust之后奪得亞軍。

gRPC是現(xiàn)代的開源遠(yuǎn)程過程調(diào)用框架。gRPC有許多令人興奮的功能:實時流傳輸,端到端代碼生成以及強大的跨平臺支持。

結(jié)果基于.NET 5中完成的工作。基準(zhǔn)測試表明.NET 5服務(wù)器性能比.NET Core 3.1快60%。.NET 5客戶端性能比.NET Core 3.1快230%。

本文我們就一起來學(xué)習(xí)下.NET 5究竟使用什么黑魔法能讓性能如此大幅度的提高。

減少內(nèi)存分配

去年,Microsoft給CNCF提供了.NET的gRPC的新實現(xiàn)。該框架建立在Kestrel和HttpClient之上的,gRPC成為.NET生態(tài)系統(tǒng)的一流成員。

gRPC使用HTTP/2作為其基礎(chǔ)協(xié)議。當(dāng)涉及到性能時,快速的HTTP/2實現(xiàn)是最重要的因素。.NET的gRPC服務(wù)器基于Kestrel建立,Kestrel是用C#編寫的HTTP服務(wù)器,其設(shè)計中關(guān)注立足于性能,在TechEmpower基準(zhǔn)測試中的性能最高的選手之一。而gRPC會自動從Kestrel的許多性能改進(jìn)中受益。但是,.NET 5中進(jìn)行了許多HTTP/2特定的優(yōu)化。

減少內(nèi)存分配是首先優(yōu)化的部分。減少每個HTTP/2請求內(nèi)存分配,就能減少垃圾回收(GC)的時間。

下面是請求超過10w個gRPC請求時候的性能分析器:

活動對象圖的鋸齒形圖案表示內(nèi)存在建立,然后進(jìn)行了垃圾回收。每個請求大約要分配3.9KB。

通過在HTTP / 2連接中添加了連接池,每個請求的內(nèi)存分配減少了一半。它可支持對內(nèi)部類型(如Http2Stream和)和公共可訪問類型(如HttpContext和HttpRequest)請求重用。

合并流后,可以進(jìn)行一系列優(yōu)化:

  • 重用輸入和輸出Pipe實例。
  • 重用已知的標(biāo)頭字符串值。與頭重用有關(guān),添加HTTP/偽裝頭作為已知頭。String分配使用倒數(shù)第三字節(jié)。
  • 重用了一些較小的按請求對象。
  • 當(dāng)服務(wù)器處于負(fù)載狀態(tài)時,連接池非常有用,但是也需要釋放不再使用的內(nèi)存。如果最近5秒鐘內(nèi)HTTP請求沒有使用,則從連接池中刪除該流。

還有許多較小的減少內(nèi)存分配的方法:

  • 刪除Kestrel的HTTP/2流控制中的分配。
  • 每當(dāng)觸發(fā)流控制時,可重置的ManualResetValueTaskSourceCore類型將替換分配新對象。
  • 驗證HTTP請求路徑時,將數(shù)組分配替換為stackalloc。
  • 消除了一些與日志記錄有關(guān)的意外分配。
  • 如果任務(wù)已經(jīng)完成,避免分配。
  • 最后通過特殊的Taskcontent-length 0字節(jié)保存字符串分配。

經(jīng)過優(yōu)化后,.NET 5中的每個請求內(nèi)存分配只有330B,減少了92%。優(yōu)化后鋸齒圖案不再出現(xiàn)。這樣在服務(wù)器處理10w個gRPC調(diào)用時,垃圾收集也不再會運行。

從Kestrel中讀取HTTP標(biāo)頭

HTTP/2連接支持通過TCP Socket的并發(fā)請求,這個功能稱為多路復(fù)用。它允許HTTP/2有效利用連接,但是一次只能處理一個連接上的一個請求的標(biāo)頭。HTTP/2的HPack標(biāo)頭壓縮是有狀態(tài)的,并且取決于順序。處理HTTP/2標(biāo)頭是一個瓶頸,因此要盡可能快。

優(yōu)化的性能HPackDecoder。解碼器是一個狀態(tài)機,可讀取傳入的HTTP/ 2 HEADER幀。狀態(tài)機允許Kestrel在幀到達(dá)時對其進(jìn)行解碼,但是解碼器在解析每個字節(jié)之后檢查狀態(tài)。另一個問題是語義值,標(biāo)頭名稱和值被復(fù)制了多次。該PR的優(yōu)化包括:

  • 加強解析循環(huán)。例如,如果剛剛解析了標(biāo)頭名稱,則該值必須在后面。無需檢查狀態(tài)機即可確定下一個狀態(tài)。
  • 跳過所有語義解析。HPack中的文字具有長度前綴。如果知道接下來的100個字節(jié)是語義,則無需檢查每個字節(jié)。標(biāo)記語義的位置并在其末尾繼續(xù)解析。
  • 避免復(fù)制語義字節(jié)。以前,原義字節(jié)在傳遞給Kestrel之前總是復(fù)制到中間數(shù)組。在大多數(shù)情況下,這不是必需的,而是可以對原始緩沖區(qū)進(jìn)行切片,然后將ReadOnlySpan傳遞給Kestrel。

這些更改一起顯著減少了解析標(biāo)頭所需的時間。標(biāo)頭大小幾乎不再成了影響因素。解碼器標(biāo)記值的開始和結(jié)束位置,然后切片該范圍。

  1. [Benchmark] 
  2. public void SmallDecode() => 
  3. _decoder.Decode(_smallHeader, endHeaders: true, handler: _noOpHandler); 
  4.  
  5.  
  6. [Benchmark] 
  7. public void LargeDecode() => 
  8. _decoder.Decode(_largeHeader, endHeaders: true, handler: _noOpHandler); 

結(jié)果:

標(biāo)頭解碼后,Kestrel需要對其進(jìn)行驗證和處理。例如,特殊的HTTP/2標(biāo)頭:path和:method需要設(shè)置到HttpRequest.Path和HttpRequest.Method上,而其他標(biāo)頭需要轉(zhuǎn)換為字符串并添加到HttpRequest.Headers集合中。

Kestrel具有已知請求標(biāo)頭的概念。已知標(biāo)頭是對常見請求標(biāo)頭的選擇,這些請求標(biāo)頭已針對快速設(shè)置和獲取進(jìn)行了優(yōu)化。為將HPack靜態(tài)表頭設(shè)置為已知頭添加了一條甚至更快的路徑。HPack靜態(tài)表給出了61點共同的報頭的名稱和值可被發(fā)送,而不是全名的數(shù)ID。具有靜態(tài)表ID的標(biāo)頭可以使用優(yōu)化的路徑繞過某些驗證,并可以根據(jù)其ID快速在集合中進(jìn)行設(shè)置。為具有名稱和值的靜態(tài)表ID添加了額外的優(yōu)化。

添加HPack響應(yīng)壓縮

在.NET 5之前,Kestrel支持讀取請求中的HPack壓縮標(biāo)頭,但不壓縮響應(yīng)標(biāo)頭。響應(yīng)頭壓縮的明顯優(yōu)勢是網(wǎng)絡(luò)使用量減少,但同時也具有性能優(yōu)勢。為壓縮的標(biāo)頭寫入幾個位比將標(biāo)頭的全名和值編碼并寫入字節(jié)更快。

添加了初始HPack靜態(tài)壓縮。靜態(tài)壓縮非常簡單:如果標(biāo)頭位于HPack靜態(tài)表中,則編寫ID來標(biāo)識標(biāo)頭,而不是較長的名稱。

動態(tài)HPack標(biāo)頭壓縮更加復(fù)雜,但也帶來了更大的收益。在動態(tài)表中跟蹤響應(yīng)頭的名稱和值,并分別為其分配一個ID。寫入響應(yīng)的標(biāo)題后,服務(wù)器將檢查表中是否包含標(biāo)題名稱和值。如果匹配,則寫入ID。如果沒有,則寫入完整的標(biāo)頭,并將其添加到表中以進(jìn)行下一個響應(yīng)。動態(tài)表有最大大小,因此向其添加標(biāo)題可能會以先進(jìn)先出的順序逐出其他標(biāo)題。

添加了動態(tài)HPack頭壓縮。為了快速搜索頭,動態(tài)表使用基本哈希表對頭條目進(jìn)行分組。為了跟蹤順序并清理除舊的標(biāo)頭,會維護(hù)一個鏈接列表。為了避免分配,已刪除的條目將被合并并重新使用。

使用Wireshark抓包,可以看到示例中g(shù)RPC調(diào)用的標(biāo)頭壓縮對響應(yīng)大小的影響。.NET Core 3.x寫入77 B,而.NET 5僅為12B。

Protobuf消息序列化

.NET的gRPC使用Google.Protobuf包作為消息的默認(rèn)序列化程序。Protobuf是一種有效的二進(jìn)制序列化格式。Google.Protobuf是為提高性能而設(shè)計的,它使用代碼生成而不是反射來序列化.NET對象??梢韵蚱渲刑砑右恍┈F(xiàn)代的.NET API和功能,以減少分配并提高效率。

Google.Protobuf最大的改進(jìn)是現(xiàn)代.NET IO類型的支持:Span,ReadOnlySequence和IBufferWriter。這些類型允許使用Kestrel公開的緩沖區(qū)直接序列化gRPC消息。這樣可以省去Google.Protobuf在序列化和反序列化Protobuf內(nèi)容時分配中間數(shù)組的麻煩。對Protobuf緩沖區(qū)序列化的支持是Microsoft和Google工程師之間多年的努力。更改分布在多個存儲庫中。

優(yōu)化對Google.Protobuf緩沖區(qū)序列化的支持。這是迄今為止最大,最復(fù)雜的變化。Protobuf讀寫使用添加到C#和.NET Core的許多面向性能的功能和API:

Span和C# ref struct類型可以快速安全地訪問內(nèi)存。Span表示任意內(nèi)存的連續(xù)區(qū)域。使用span使我們可以序列化為托管.NET數(shù)組,堆棧分配的數(shù)組或非托管內(nèi)存,而無需使用指針。Span和.NET可以防止緩沖區(qū)溢出。

stackalloc用于創(chuàng)建基于堆棧的數(shù)組。stackalloc是在需要較小緩沖區(qū)時避免分配的有用工具。

增加MemoryMarshal.GetReference(),Unsafe.ReadUnaligned()和Unsafe.WriteUnaligned()等低級方法,可以實現(xiàn)在原始類型和字節(jié)之間直接轉(zhuǎn)換。

BinaryPrimitives具有用于在.NET基本類型和字節(jié)之間進(jìn)行有效轉(zhuǎn)換的輔助方法。例如,BinaryPrimitives.ReadUInt64讀取小數(shù)字節(jié)并返回?zé)o符號的64位數(shù)字。LittleEndianBinaryPrimitive提供的方法經(jīng)過了最優(yōu)化,并使用了向量化。

關(guān)于現(xiàn)代C#和.NET的一大優(yōu)點是可以在不犧牲內(nèi)存安全性的情況下編寫快速,高效,低級的庫。在性能方面,可以極大的壓榨你的服務(wù)器:

  1. private TestMessage _testMessage = CreateMessage(); 
  2. private ReadOnlySequence<byte> _testData = CreateData(); 
  3. private IBufferWriter<byte> _bufferWriter = CreateWriter(); 
  4. [Benchmark] 
  5. public IMessage ToByteArray() => 
  6. _testMessage.ToByteArray(); 
  7. [Benchmark] 
  8. public IMessage ToBufferWriter() => 
  9. _testMessage.WriteTo(_bufferWriter); 
  10. [Benchmark] 
  11. public IMessage FromByteArray() => 
  12. TestMessage.Parser.ParseFrom(CreateBytes()); 
  13. [Benchmark] 
  14. public IMessage FromSequence() => 
  15. TestMessage.Parser.ParseFrom(_testData); 

給Google.Protobuf添加對緩沖區(qū)序列化的支持只是第一步。要使用gRPC for .NET,需要更多工作才能利用新功能:

向Grpc.Core.Api中的gRPC序列化抽象層添加了ReadOnlySequence API和IBufferWriter

API。

更新gRPC代碼生成,以將Google.Protobuf中的更改粘貼到Grpc.Core.Api。

更新了.NET的gRPC,以使用Grpc.Core.Api中的新序列化抽象。這段代碼是Kestrel和gRPC之間的集成。由于Kestrel的IO建立在System.IO.Pipelines之上,因此可以在序列化過程中使用其緩沖區(qū)。

最終結(jié)果是gRPC for .NET將Protobuf消息直接序列化到Kestrel的請求和響應(yīng)緩沖區(qū)。中間數(shù)組分配和字節(jié)副本已從gRPC消息序列化中刪除。

總結(jié)

性能是.NET和gRPC的基本功能,隨著云應(yīng)用崛起,性能變得越來越重要。較低的延遲和較高的吞吐量意味著更少的服務(wù)器。高性能的應(yīng)用可以節(jié)省金錢,減少能耗和構(gòu)建綠色應(yīng)用程序的機會。

gRPC,Protobuf和.NET 5進(jìn)行大量的嘗試和更改,用來提高性能?;鶞?zhǔn)測試表明,gRPC服務(wù)器RPS提高了60%,gRPC客戶端RPS提高了230%。

 

責(zé)任編輯:趙寧寧 來源: 蟲蟲搜奇
相關(guān)推薦

2009-06-29 18:04:13

Tapestry5

2021-06-10 09:40:12

C++性能優(yōu)化Linux

2010-01-21 09:34:57

C++語法

2010-01-27 16:05:06

C++堆棧

2012-02-15 09:36:50

C++ 11

2021-09-13 05:02:49

GogRPC語言

2025-01-13 06:00:00

Go語言gRPC

2009-08-21 15:06:09

C#連接字符串

2022-04-29 11:52:02

API代碼HTTP

2010-01-21 14:07:14

CC++聲明

2011-04-11 13:00:08

C++結(jié)構(gòu)體枚舉

2010-01-20 10:19:55

C++數(shù)組

2010-01-21 13:33:44

C++基類

2010-01-25 10:25:19

C++變量

2010-01-28 16:31:54

C++類型

2010-01-27 17:16:52

C++構(gòu)造函數(shù)

2020-08-21 13:20:36

C++If ElseLinux

2022-02-20 23:15:46

gRPCGolang語言

2010-01-26 10:42:26

C++函數(shù)

2011-05-12 18:21:42

C++
點贊
收藏

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