Mqttnet內存與性能改進錄

1 MQTTnet介紹

MQTTnet是一個高性能的 .NET MQTT庫,它提供MQTT客戶端和MQTT服務器的功能,支持到最新MQTT5協議版本,支持.Net Framework4.5.2版本或以上。

MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker) and supports the MQTT protocol up to version 5. It is compatible with mostly any supported .NET Framework version and CPU architecture.

2 我與MQTTnet

我有一些小型項目,需要安裝在局域網環境下的windows或linux系統,這個安裝過程需要小白也能安裝,而且每天都有可能有多份新的安裝部署的新環境,所以流行的mqtt服務器emqx可能變得不太適合我的選型,因為讓小白來大量部署它不是非常方便。

我的這個小項目主體是一個Web項目,瀏覽器用戶對象是管理員,數據的產生者是N多個廉價linux小型設備,設備使用mqtt協議高頻提交數據到后臺,后臺也需要使用mqtt協議來主動控制設備完成一些操作動作。除此之后,Web瀏覽器也需要使用mqtt over websocket來訂閱一些主題,達到監控某臺設備的實時數據目的。

經過比較,MQTTnet變成了我意向使用的mqtt庫,尤其是MQTTnet.AspNetCore子項目,基于kestrel來使用tcp或websocket做傳輸層,增加mqtt應用層協議的解析,最后讓mqtt與asp.netcore完美地融合在一起。

3 Bug發現

項目有后臺主動發送mqtt到設備以控制設備的需求,在mqttnet里有個對應的InjectApplicationMessage()擴展方法可以從server主動發送mqtt到client,但這個方法總是拋出ArgumentNullException。但如果使用InjectApplicationMessage (InjectedMqttApplicationMessage)這個基礎方法來注入mqtt消息不有異常。

經過一段時間后,閑時的我決定遷出mqttnet項目的源代碼來調試分析。最后發現是因為這個擴展方法沒有傳遞SenderClientId導致的異常,所以我決定嘗試修改并推送一個請求到mqttnet項目。

5e80f1692689d0300c4c53b505c9d135.png

4 改進之路

經過嘗試修改一個小小bug之后,我開始認真的閱讀MQTTnet.AspNetCore的源代碼,陸續發現一些可以減少內存復制和內存分配的優化點:

  1. ReadOnlyMemory<byte>轉為ReceivedMqttPacket過程優化;

  2. MqttPacketBuffer發送過程的優化;

  3. Array.Copy()的改進;

  4. Byte[]?->?ArraySegment<byte>的優化;

4.1 避免不必要的ReadOnlyMemory<byte>轉為byte[]

原始代碼

var bodySlice = copy.Slice(0, bodyLength);
var buffer = bodySlice.GetMemory().ToArray();
var receivedMqttPacket = new ReceivedMqttPacket(fixedHeader, new ArraySegment<byte>(buffer, 0, buffer.Length), buffer.Length + 2);static ReadOnlyMemory<byte> GetMemory(this in ReadOnlySequence<byte> input)
{if (input.IsSingleSegment){return input.First;}// Should be rarereturn input.ToArray();
}

原始代碼設計了一個GetMemory()方法,目的是在兩個地方調用到。但它的一句var buffer = bodySlice.GetMemory().ToArray(),就會無條件的產生一次內存分配和一次內存拷貝。

改進代碼

var bodySlice = copy.Slice(0, bodyLength);
var bodySegment = GetArraySegment(ref bodySlice); 
var receivedMqttPacket = new ReceivedMqttPacket(fixedHeader, bodySegment, headerLength + bodyLength);static ArraySegment<byte> GetArraySegment(ref ReadOnlySequence<byte> input)
{if (input.IsSingleSegment && MemoryMarshal.TryGetArray(input.First, out var segment)){return segment;}// Should be rarevar array = input.ToArray();return new ArraySegment<byte>(array);
}

因為有其它地方的優化,GetMemory()不再需要復用,所以我們直接改為GetArraySegment(),里面使用MemoryMarshal.TryGetArray()方法嘗試從ReadOnlyMemory<byte>獲取ArraySegment<byte>對象。而mqttnet的ReceivedMqttPacket對象是支持ArraySegment<byte>類型參數的。

054bac89b702345ed6cd81c559a7e0c0.png

在我提交請求之后,@gfoidl給了很多其它特別好的性能方面的建議,有興趣的同學可以點此查看。

戲劇性的是,在我嘗試改進這個問題的時候,我發現了mqttnet的另外一個BUG:當bodySegment的Offset不是0開始的時候,mqttnet會產生異常。這足以說明,mqttnet項目從未使用Offset大于0的ArraySegment<byte>,所以這個bug才一直沒有發現。本為不是MQTTnet.AspNetCore子項目的代碼我就不改的原則,我向mqttnet提了問題:https://github.com/dotnet/MQTTnet/issues/1592 作者也很認真看待這個問題,于是自己加班解決:https://github.com/dotnet/MQTTnet/pull/1593

更戲劇性的是,我開心地合并main代碼過來驗證之后,發現作者改的BUG里又帶入了BUG!現在Offset大于0還是有問題。于是我心急啊,我決定為這個BUG中BUG提交一個修改的請求:https://github.com/dotnet/MQTTnet/pull/1598

6ab02cdca7729ab714d1c6d24d58aae3.png

最后,這個MemoryMarshal.TryGetArray()的優化終于提到合并,改進后CPU時間時間也減少了,內存分配更是減少了50%。

4.2?MqttPacketBuffer發送過程的優化

MqttPacketBuffer有兩個數據段:Pacaket段和Payload段,我看到它原始發送代碼如下:

var buffer = formatter.Encode(packet);
var msg = buffer.Join().AsMemory();
var output = _output;
var result = await output.WriteAsync(msg, cancellationToken).ConfigureAwait(false);

我也沒有經過認證思考,覺得這里可以將Pacaket段和Payload直接兩次發送即可。

var buffer = PacketFormatterAdapter.Encode(packet);
await _output.WriteAsync(buffer.Packet, cancellationToken).ConfigureAwait(false);if (buffer.Payload.Count > 0)
{ await _output.WriteAsync(buffer.Payload, cancellationToken).ConfigureAwait(false);
}

后來作者說,當mqtt over websocket時,有些客戶端在實現上沒能兼容一個mqtt包分多個websocket幀傳輸的處理,所以需要合并發送。那我就想,如果我檢測傳輸層是websocket的話再Join合并就行了,于是改為如下:

if (_isOverWebSocket == false)
{await _output.WriteAsync(buffer.Packet, cancellationToken).ConfigureAwait(false);if (buffer.Payload.Count > 0){await _output.WriteAsync(buffer.Payload, cancellationToken).ConfigureAwait(false);}
}
else
{     var bufferSegment = buffer.Join();await _output.WriteAsync(bufferSegment, cancellationToken).ConfigureAwait(false);
}

雖然覺得這個方案比之前要好了一些,但感覺Jion里的?new byte[]的分配讓我耿耿于懷。再經過幾將進改,最后的代碼如下,雖然也有拷貝,但至少已經沒有分配:

if (buffer.Payload.Count == 0)
{// zero copy// https://github.com/dotnet/runtime/blob/main/src/libraries/System.IO.Pipelines/src/System/IO/Pipelines/StreamPipeWriter.cs#L279await _output.WriteAsync(buffer.Packet, cancellationToken).ConfigureAwait(false);
}
else
{WritePacketBuffer(_output, buffer);await _output.FlushAsync(cancellationToken).ConfigureAwait(false);
}static void WritePacketBuffer(PipeWriter output, MqttPacketBuffer buffer)
{// copy MqttPacketBuffer's Packet and Payload to the same buffer block of PipeWriter// MqttPacket will be transmitted within the bounds of a WebSocket frame after PipeWriter.FlushAsyncvar span = output.GetSpan(buffer.Length);buffer.Packet.AsSpan().CopyTo(span);buffer.Payload.AsSpan().CopyTo(span.Slice(buffer.Packet.Count));output.Advance(buffer.Length);
}

4.3?Array.Copy()的改進

mqttnet由于要兼容很多.net框架和版本,所以往往能使用的api不多,比如在內存拷貝了,還保留了最初的Array.Copy(),我們可以較新的框架下使用更好的api來復制,最高可達25%的復制性能提升,這個改進的工作量非常小,但產出是相當的可喜啊。

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Copy(byte[] source, int sourceIndex, byte[] destination, int destinationIndex, int length)
{
#if NETCOREAPP3_1_OR_GREATER || NETSTANDARD2_1source.AsSpan(sourceIndex, length).CopyTo(destination.AsSpan(destinationIndex, length));
#elif NET461_OR_GREATER || NETSTANDARD1_3_OR_GREATERunsafe{fixed (byte* pSoure = &source[sourceIndex]){fixed (byte* pDestination = &destination[destinationIndex]){System.Buffer.MemoryCopy(pSoure, pDestination, length, length);}}}
#elseArray.Copy(source, sourceIndex, destination, destinationIndex, length);
#endif
}

4.4?Byte[]?->?ArraySegment<byte>的優化

當前的mqttnet,由于歷史設計的局限原因,現在還不能創建ArraySegment<byte>Memory<byte>作為payload的mqtt消息包。如果我們從ArrayPool申請1000字節的buffer,實際我們會得到一個到1024字節的buffer,想拿租賃的buffer的前1000字節做mqtt消息的payload,我們現在不得不再創建一個1000字節的byte[1000]?newpayload,然后拷貝buffer到newpayload。

這種局限對服務端來說弊端是很大的,我現在嘗試如何不破壞原始的byte[]支持的設計提前下,讓mqttnet也支持ArraySegment<byte>的數據發送。當然,保持兼容性的新Api加入對項目來說是一種大的變化,自然有一定的風險性。

如果你也關注這個mqttnet項目,你可以查看 https://github.com/dotnet/MQTTnet/pull/1585 這個提議,也許未來它會變成現實。

5 最后

開源項目讓大眾受益,尤其是核心作者真的不容易,為其嘔心瀝血。我們在受益的同時,如果有能力的話可以反撫開源項目,在參與過程中,自身也會學到一些知識的,就當作被學習的過程吧。

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

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

相關文章

DataArtisans戰略聯手阿里云 Apache Flink服務能力云化

近日&#xff0c;Apache Flink商業公司 CEO、聯合創始人Kostas Tzoumas在云棲大會上宣布和阿里集團達成戰略合作伙伴關系&#xff0c;希望能夠借助全球最大的云計算公司之一阿里云&#xff0c;服務更多的大數據實時流計算的客戶。同時期待通過加強和阿里集團技術合作&#xff0…

高清、免版權美圖資源大全

正所謂“一圖勝千言”&#xff0c;當在寫文章、做設計、搞 PPT、發朋友圈&#xff0c;搭配一些合適的圖&#xff0c;這無疑將極大提升內容的表現力。鑒于此&#xff0c;在傾城之鏈的美圖板塊&#xff0c;收錄了來自世界各地的優質圖片網站&#xff0c;它們所提供高品質且免費的…

如何在WhatsApp中將群聊靜音

Group Chats are awesome if you’re in a club, want to keep in touch with all your friends, or are trying organize something. Unfortunately, if you’re busy and the other members decide to have a long, detailed conversation about the latest episode of Game …

Django進階之session

Django進階之session 基于cookie做用戶驗證時&#xff1a;敏感信息不適合放在cookie中 session依賴cookie session原理 cookie是保存在用戶瀏覽器端的鍵值對 session是保存在服務器端的鍵值對 session服務端中存在的數據為&#xff1a; session {隨機字符串1&#xff1a;{用戶…

Facebook開源 PyTorch版 fairseq,準確性最高、速度比循環神經網絡快9倍

今年5月&#xff0c;Facebook AI研究院&#xff08;FAIR&#xff09;發表了他們的研究成果fairseq&#xff0c;在fairseq中&#xff0c;他們使用了一種新型的卷積神經網絡來做語言翻譯&#xff0c;比循環神經網絡的速度快了9倍&#xff0c;而且準確性也是現有模型中最高的。此外…

推薦一個開源的現代化的 PDF 生成組件

你好&#xff0c;這里是 Dotnet 工具箱&#xff0c;定期分享 Dotnet 有趣&#xff0c;實用的工具和組件&#xff0c;希望對您有用&#xff01;前言QuestPDF 是一個開源免費的 .NET 組件庫&#xff0c;可以用來生成 PDF 文檔。在 Github 上有4千多的 Star。項目充分考慮了 PDF 文…

小程序調用阿里云身份證識別OCR(附帶七牛云上傳圖片)

寫在前面&#xff1a;實現的邏輯是拍照上傳調用后端封裝好的身份證接口&#xff0c;然后同時調用七牛云接口把照片傳過去以便后臺管理系統審核看1:首選需要這么一張頁面接下來就寫我是怎么做的首先是布局&#xff08;以下是wxml&#xff09; <view><view classidcard&…

windows 安裝yaml支持和pytest支持等

打開cmd 輸入pip install pyyaml #yaml文件支持 輸入pip install pytest #pytest框架支持 輸入pip install requests #requests接口測試支持 輸入pip install pyopenssl #openssl支持 前提是電腦上的python已經配置好了轉載于:https://www.cnblogs.com/mghhzAnne/p/92…

史上最好記的神經網絡結構速記表(上)

本文講的是史上最好記的神經網絡結構速記表&#xff08;上&#xff09;&#xff0c;新的神經網絡結構不斷涌現&#xff0c;我們很難一一掌握。哪怕一開始只是記住所有的簡稱&#xff08; DCIGN&#xff0c;BiLSTM&#xff0c;DCGAN &#xff09;&#xff0c;也會讓同學們吃不消…

厚積薄發,微軟OFFICE云時代宏腳本來臨,Excel Srcipt已經推進到桌面端可用

前一陣子&#xff0c;已經發現微軟在Excel上發布了Office Script For Excel&#xff0c;當時只能在網頁端的Excel上使用&#xff0c;今天打開桌面端的Excel&#xff0c;發現多了一個【自動執行】選項卡。再一次看了下&#xff0c;比起以前的Office Addin&#xff0c;要先進得多…

如何使用Amazon Echo控制您的Eero Wi-Fi網絡

Thanks to the power of Alexa and its open API, you’re able to control a vast number of devices using just your voice. If you have an Eero Wi-Fi system, you can even control your home network with the Amazon Echo. 得益于Alexa的強大功能及其開放的API&#xf…

H5在WebView上開發小結

背景 來自我司業務方要求&#xff0c;需開發一款APP。但由于時間限制&#xff0c;只能采取套殼app方式&#xff0c;即原生app內嵌webview展示前端頁面。本文主要記述JavaScript與原生app間通信&#xff0c;以及內嵌webview開發時&#xff0c;前端方面可能踩的一些坑。 技術架構…

C#的?和??

1.&#xff1f;&#xff1f; 為了實現Nullable數據類型轉換成non-Nullable類型數據&#xff0c;才有的一個操作符&#xff1b; 意義&#xff1a;一變量取值&#xff0c;取符號左邊的值&#xff0c;若左邊為null&#xff0c;那么取賦值&#xff1f;&#xff1f;右邊的&#xff1…

odoo 自定義視圖_如何使用Windows的五個模板自定義文件夾視圖

odoo 自定義視圖If you’re particular about how Windows displays the contents of your folders, you can cut your customization time down considerably by taking advantage of File Explorer’s five built-in folder templates. 如果您特別想知道Windows如何顯示文件夾…

C#之ILC和C++的CLR前者更快?

楔子ILC是C#寫的&#xff0c;CLR是C。.Net 7中&#xff0c;為何微軟執意用一個托管的模型去嘗試取代非托管框架呢&#xff1f;至少native code方面它是這么做的這個問題一直縈繞腦海。非托管和托管十年前出版的那本久負盛名的《CLR via C#》至今都是不可或缺的存在&#xff0c;…

歷史

python的歷史 kfsaldkfsdf fdskfdsa fdsjkafsjda fdshkfjsdja View Codefjdskaffdsjkaffdsjakflsad;fjdsklaf 轉載于:https://www.cnblogs.com/jin-xin/articles/10448286.html

typescript+react+antd基礎環境搭建

typescriptreactantd基礎環境搭建&#xff08;包含樣式定制&#xff09; tsconfig.json 配置 // 具體配置可以看上面的鏈接 這里module moduleResolution的配置都會影響到antd的顯示 // allowSyntheticDefaultImports 是antd官網給的配置 必須加上 {"compilerOptions&quo…

最小生成樹Prim算法和Kruskal算法

https://www.cnblogs.com/JoshuaMK/p/prim_kruskal.html 轉載于:https://www.cnblogs.com/DixinFan/p/9225105.html

如何重新打開Windows防火墻提示?

If you are setting up a new program that needs network access, but are not paying close enough attention, you might end up accidentally causing Windows firewall to block the program. How do you fix such a mistake? Today’s SuperUser Q&A post helps a f…

判斷字符串出現次數最多的字符 及 次數

分析 題目的意思大致就是找出每個字符出現的次數&#xff0c;然后比較大小。那么每個字符都應該對應它出現的次數。既然是一一對應的&#xff0c;那我們就想到用對象的key和value來儲存字符和其出現的次數。具體做法 新建一個空對象obj 遍歷給定的字符串接下來就是最重要的 把字…