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

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

先说结论


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中的所有块默认是单线程的,这就意味着完成以上两步的TransfromBlockActionBlock都是以一个线程挨个处理消息数据 (这也是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);
            }   
        }
    }
}


测试结果如下:


e94c08033d353d51ba51fcbbb84da9b9.png


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


优化后的Actor模型


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


b62d4f20288e6fe6b6d1f1c266d3dee3.png


请重新回看《三分钟掌握共享内存 & 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


56a2bdb74f712d36c752eb13af7d76ed.png


结束语


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

相关文章
|
SQL 开发框架 安全
三分钟掌握共享内存 & Actor并发模型
吃点好的,很有必要。今天介绍常见的两种并发模型:共享内存&Actor
三分钟掌握共享内存 & Actor并发模型
|
4月前
|
存储 分布式计算 Hadoop
HadoopCPU、内存、存储限制
【7月更文挑战第13天】
293 14
|
3月前
|
存储 编译器 C语言
【C语言篇】数据在内存中的存储(超详细)
浮点数就采⽤下⾯的规则表⽰,即指数E的真实值加上127(或1023),再将有效数字M去掉整数部分的1。
383 0
|
28天前
|
存储 C语言
数据在内存中的存储方式
本文介绍了计算机中整数和浮点数的存储方式,包括整数的原码、反码、补码,以及浮点数的IEEE754标准存储格式。同时,探讨了大小端字节序的概念及其判断方法,通过实例代码展示了这些概念的实际应用。
59 1
|
1月前
|
存储
共用体在内存中如何存储数据
共用体(Union)在内存中为所有成员分配同一段内存空间,大小等于最大成员所需的空间。这意味着所有成员共享同一块内存,但同一时间只能存储其中一个成员的数据,无法同时保存多个成员的值。
|
1月前
|
存储 弹性计算 算法
前端大模型应用笔记(四):如何在资源受限例如1核和1G内存的端侧或ECS上运行一个合适的向量存储库及如何优化
本文探讨了在资源受限的嵌入式设备(如1核处理器和1GB内存)上实现高效向量存储和检索的方法,旨在支持端侧大模型应用。文章分析了Annoy、HNSWLib、NMSLib、FLANN、VP-Trees和Lshbox等向量存储库的特点与适用场景,推荐Annoy作为多数情况下的首选方案,并提出了数据预处理、索引优化、查询优化等策略以提升性能。通过这些方法,即使在资源受限的环境中也能实现高效的向量检索。
|
1月前
|
存储 编译器
数据在内存中的存储
数据在内存中的存储
42 4
|
1月前
|
存储 Java
JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配
这篇文章详细地介绍了Java对象的创建过程、内存布局、对象头的MarkWord、对象的定位方式以及对象的分配策略,并深入探讨了happens-before原则以确保多线程环境下的正确同步。
56 0
JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配
|
1月前
|
存储 机器学习/深度学习 人工智能
数据在内存中的存储
数据在内存中的存储
|
1月前
|
存储 C语言
深入C语言内存:数据在内存中的存储
深入C语言内存:数据在内存中的存储