共享内存 Actor并发模型到底哪个快?
HI,前幾天被.NET圈紀檢委@懶得勤快問到共享內存和Actor并發模型哪個速度更快。
前文傳送門:《三分鐘掌握共享內存 & Actor并發模型》
說實在,我內心10w頭羊駝跑過.....
先說結論
1.首先兩者對于并發的風格模型不一樣。
共享內存利用多核CPU的優勢,使用強一致的鎖機制控制并發, 各種鎖交織,稍不注意可能出現死鎖,更適合熟手。
Actor模型易于控制和管理,以消息觸發、流水線挨個處理,天然分布式,思路清晰。
2.真要說性能,求100_000 以內的素數的個數]場景?&?電腦8c 16g的配置
?2.1 理論上如果以默認的Actor并發模型來做這個事情,共享內存模型是優于Actor模型的;?2.2 上文中我對于Actor做了多線程優化,Actor模型性能慢慢追上來了。
下面請聽我嘮嗑。
默認Actor模型
計算[100_000內素數的個數], 分為兩步:
(1) 迭代判斷當前數字是不是素數
(2) 如果是素數,執行sum++
完成以上兩步,共享內存模型均能充分利用CPU多核心。
Actor模型:與TPL中的原語不同,TPL Datflow中的所有塊默認是單線程的,這就意味著完成以上兩步的TransfromBlock和ActionBlock都是以一個線程挨個處理消息數據?(這也是Dataflow的設計初衷,形成清晰單純的流水線)。
猜測此時:共享內存相比默認的Actor模型更具優勢。
使用NUnit做單元測試,數據量從小到大: 10_000,50_000,100_000,200_000,300_000,500_000
using?NUnit.Framework; using?System; using?System.Threading.Tasks; using?System.Collections.Generic; using?System.Threading; using?System.Threading.Tasks.Dataflow;namespace?TestProject2 {public?class?Tests{[TestCase(10_000)][TestCase(50_000)][TestCase(100_000)][TestCase(200_000)][TestCase(300_000)][TestCase(500_000)]public?void?ShareMemory(int?num){var?sum?=?0;Parallel.For(1,?num?+?1,?(x,?state)?=>{var?f?=?true;if?(x?==?1)f?=?false;for?(int?i?=?2;?i?<=?x?/?2;?i++){if?(x?%?i?==?0)??//?被[2,x/2]任一數字整除,就不是質數f?=?false;}if?(f?==?true){Interlocked.Increment(ref?sum);//?共享了sum對象,“++”就是調用sum對象的成員方法}});Console.WriteLine($"1-{num}內質數的個數是{sum}");}[TestCase(10_000)][TestCase(50_000)][TestCase(100_000)][TestCase(200_000)][TestCase(300_000)][TestCase(500_000)]public?async?Task?Actor(int?num){var?linkOptions?=?new?DataflowLinkOptions?{?PropagateCompletion?=?true?};var?bufferBlock?=?new?BufferBlock<int>();var?transfromBlock?=?new?TransformBlock<int,?bool>(x?=>{var?f?=?true;if?(x?==?1)f?=?false;for?(int?i?=?2;?i?<=?x?/?2;?i++){if?(x?%?i?==?0)??//?被[2,x/2]任一數字整除,就不是質數f?=?false;}return?f;},?new?ExecutionDataflowBlockOptions?{?EnsureOrdered?=?false?});var?sum?=?0;var?actionBlock?=?new?ActionBlock<bool>(x?=>{if?(x?==?true)sum++;},?new?ExecutionDataflowBlockOptions?{??EnsureOrdered?=?false?});transfromBlock.LinkTo(actionBlock,?linkOptions);//?準備從pipeline頭部開始投遞try{var?list?=?new?List<int>?{?};for?(int?i?=?1;?i?<=?num;?i++){var?b?=?await?transfromBlock.SendAsync(i);if?(b?==?false){list.Add(i);}}if?(list.Count?>?0){Console.WriteLine($"md,num?post?failure,num:{list.Count},post?again");//?再投一次foreach?(var?item?in?list){transfromBlock.Post(item);}}transfromBlock.Complete();??//?通知頭部,不再投遞了;?會將信息傳遞到下游。actionBlock.Completion.Wait();??//?等待尾部執行完Console.WriteLine($"1-{num}?Prime?number?include?{sum}");}catch?(Exception?ex){Console.WriteLine($"1-{num}?cause?exception.",ex);}???}} }測試結果如下:
測試結果印證我說的結論2.1
優化后的Actor模型
那后面我對Actor做了什么優化呢?? 能產生下圖的2.2結論。
請重新回看《三分鐘掌握共享內存 & Actor并發模型》 TransfromBlock 塊的細節:
var?transfromBlock?=?new?TransformBlock<int,?bool>(x?=>{var?f?=?true;if?(x?==?1)f?=?false;for?(int?i?=?2;?i?<=?x?/?2;?i++){if?(x?%?i?==?0)??//?被[2,x/2]任一數字整除,就不是質數f?=?false;}return?f;},?new?ExecutionDataflowBlockOptions?{?MaxDegreeOfParallelism=50,?EnsureOrdered?=?false?}); // 這里開啟多線程并發上面說到默認的Actor是以單線程處理輸入的消息,此次我們對這個TransfromBlock 塊設置了MaxDegreeOfParallelism?參數,
這個參數能在Actor中開啟多線程并發執行,但是這里面就不能有共享變量(否則你又得加鎖),恰好我們完成?(1) 迭代判斷當前數字是不是素數這一步并不依賴共享對象,所以這(1)步開啟多線程以后性能與共享內存模型基本沒差別。
那為什么總體性能慢慢超過共享內存?
這是因為執行第二步(2) 如果是素數,執行sum++, 共享內存要加/解鎖,線程切換;?而Actor單線程挨個處理,?總體上Actor就略勝共享內存模型了。
這里再次強調,Actor模型執行第二步(2) 如果是素數,執行sum++,不可開啟MaxDegreeOfParallelism,因為依賴了共享變量sum
結束語
That's All, 感謝.NET圈紀檢委@懶得勤快促使我重溫了單元測試的寫法 & 深度分析Actor模型風格。
請大家仔細對比結論和上圖,脫離場景和硬件環境談性能就是耍流氓,理解不同并發模型的風格和能力是關鍵, 針對場景和未來的拓展性、可維護性、可操作性做技術選型 。
本文內容和制圖均為原創,文章永久更新地址請參閱左下角原文,如對您有所幫助,請一鍵三連,方便的話置一個星標 ~。。~。
專題相關 一網打盡
??三分鐘掌握共享內存 & Actor并發模型
?你管這叫"線程安全"?
?.Net線程同步技術解讀
?Redis分布式鎖抽絲剝繭
?看過這么多爆文,依舊走不好異步編程這條路?
?TPL Dataflow組件應對高并發,低延遲要求
?如何利用.NETCore向Azure EventHubs準實時批量發送數據?
?難纏的布隆過濾器,這次終于通透了
掃碼關注我們
不會讓您失望的。
總結
以上是生活随笔為你收集整理的共享内存 Actor并发模型到底哪个快?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [007] 详解 .NET 程序集
- 下一篇: 自定义EventSource(二)Pol