共享内存 & Actor并发模型到底哪个快?

存储 存储软件
共享内存利用多核CPU的优势,使用强一致的锁机制控制并发, 各种锁交织,稍不注意可能出现死锁,更适合熟手。

[[414358]]

本文转载自微信公众号「精益码农」,作者有态度的马甲。转载本文请联系精益码农公众号。

先说结论

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

  1. using NUnit.Framework; 
  2. using System; 
  3. using System.Threading.Tasks; 
  4. using System.Collections.Generic; 
  5. using System.Threading; 
  6. using System.Threading.Tasks.Dataflow; 
  7.  
  8. namespace TestProject2 
  9.     public class Tests 
  10.     { 
  11.         [TestCase(10_000)] 
  12.         [TestCase(50_000)] 
  13.         [TestCase(100_000)] 
  14.         [TestCase(200_000)] 
  15.         [TestCase(300_000)] 
  16.         [TestCase(500_000)] 
  17.         public void ShareMemory(int num) 
  18.         { 
  19.             var sum = 0; 
  20.             Parallel.For(1, num + 1, (x, state) => 
  21.             { 
  22.                 var f = true
  23.                 if (x == 1) 
  24.                     f = false
  25.                 for (int i = 2; i <= x / 2; i++) 
  26.                 { 
  27.                     if (x % i == 0)  // 被[2,x/2]任一数字整除,就不是质数 
  28.                         f = false
  29.                 } 
  30.                 if (f == true
  31.                 { 
  32.                     Interlocked.Increment(ref sum);// 共享了sum对象,“++”就是调用sum对象的成员方法 
  33.                 } 
  34.             }); 
  35.             Console.WriteLine($"1-{num}内质数的个数是{sum}"); 
  36.         } 
  37.  
  38.         [TestCase(10_000)] 
  39.         [TestCase(50_000)] 
  40.         [TestCase(100_000)] 
  41.         [TestCase(200_000)] 
  42.         [TestCase(300_000)] 
  43.         [TestCase(500_000)] 
  44.         public async Task Actor(int num) 
  45.         { 
  46.             var linkOptions = new DataflowLinkOptions { PropagateCompletion = true }; 
  47.             var bufferBlock = new BufferBlock<int>(); 
  48.             var transfromBlock = new TransformBlock<int, bool>(x => 
  49.             { 
  50.                 var f = true
  51.                 if (x == 1) 
  52.                     f = false
  53.                 for (int i = 2; i <= x / 2; i++) 
  54.                 { 
  55.                     if (x % i == 0)  // 被[2,x/2]任一数字整除,就不是质数 
  56.                         f = false
  57.                 } 
  58.                 return f; 
  59.             }, new ExecutionDataflowBlockOptions { EnsureOrdered = false }); 
  60.  
  61.             var sum = 0; 
  62.             var actionBlock = new ActionBlock<bool>(x => 
  63.             { 
  64.                 if (x == true
  65.                     sum++; 
  66.             }, new ExecutionDataflowBlockOptions {  EnsureOrdered = false }); 
  67.             transfromBlock.LinkTo(actionBlock, linkOptions); 
  68.             // 准备从pipeline头部开始投递 
  69.             try 
  70.             { 
  71.                 var list = new List<int> { }; 
  72.                 for (int i = 1; i <= num; i++) 
  73.                 { 
  74.                     var b = await transfromBlock.SendAsync(i); 
  75.                     if (b == false
  76.                     { 
  77.                         list.Add(i); 
  78.                     } 
  79.                 } 
  80.                 if (list.Count > 0) 
  81.                 { 
  82.                     Console.WriteLine($"md,num post failure,num:{list.Count},post again"); 
  83.                     // 再投一次 
  84.                     foreach (var item in list) 
  85.                     { 
  86.                         transfromBlock.Post(item); 
  87.                     } 
  88.                 } 
  89.                 transfromBlock.Complete();  // 通知头部,不再投递了; 会将信息传递到下游。 
  90.                 actionBlock.Completion.Wait();  // 等待尾部执行完 
  91.                 Console.WriteLine($"1-{num} Prime number include {sum}"); 
  92.             } 
  93.             catch (Exception ex) 
  94.             { 
  95.                 Console.WriteLine($"1-{num} cause exception.",ex); 
  96.             }    
  97.         } 
  98.     } 

测试结果如下:

测试结果印证我说的结论2.1

优化后的Actor模型

那后面我对Actor做了什么优化呢? 能产生下图的2.2结论。

请重新回看《三分钟掌握共享内存 & Actor并发模型》 TransfromBlock 块的细节:

  1. var transfromBlock = new TransformBlock<int, bool>(x => 
  2.     { 
  3.           var f = true
  4.           if (x == 1) 
  5.              f = false
  6.           for (int i = 2; i <= x / 2; i++) 
  7.           { 
  8.                 if (x % i == 0)  // 被[2,x/2]任一数字整除,就不是质数 
  9.                    f = false
  10.            } 
  11.            return f; 
  12.      }, 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模型风格。 

请大家仔细对比结论和上图,脱离场景和硬件环境谈性能就是耍流氓,理解不同并发模型的风格和能力是关键, 针对场景和未来的拓展性、可维护性、可操作性做技术选型 。

 

责任编辑:武晓燕 来源: 精益码农
相关推荐

2023-08-10 08:01:36

RDB数据AOF

2020-09-23 22:36:27

分布式架构系统

2021-07-06 14:47:30

Go 开发技术

2018-12-18 14:08:01

Java内存volatile

2012-11-15 10:18:11

IBMdw

2016-09-26 17:09:28

Java并发编程内存模型

2009-08-05 16:04:27

C# Actor模型

2022-02-21 10:18:13

机器学习数据模型

2020-09-22 08:22:28

快充

2022-10-27 08:31:31

架构

2023-10-27 07:47:58

Java语言顺序性

2010-01-15 09:15:09

Scala Actor并发

2022-06-07 12:03:33

Java内存模型

2022-03-16 08:39:19

StackHeap内存

2009-07-09 10:02:39

Actor模型Erlang

2017-03-02 14:52:46

2021-07-26 08:12:31

开源API网关

2017-11-22 09:00:00

2018-04-25 10:13:30

Redis内存模型

2022-03-26 09:06:40

ActorCSP模型
点赞
收藏

51CTO技术栈公众号