zoukankan      html  css  js  c++  java
  • 共享内存 & Actor并发模型哪个更快?

    HI,前几天被.NET圈纪检委@懒得勤快问到共享内存Actor并发模型哪个速度更快。

    前文传送门:

    说实在,我内心10w头羊驼跑过......

    先说结论

    1. 首先两者对于并发的风格模型不一样。

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

    Actor模型易于控制和管理,以消息触发,流水线挨个处理, 思路清晰。

    1. 真要说性能,求100000 以内的素数的个数]场景 & 我电脑8c 16g的配置, 我根据这个示例拍脑袋对比。。。。。
    • 2.1 理论上如果以默认的Actor并发模型来做这个事情,Actor的性能是逊于共享内存模型的;

    • 2.2 上文中我对于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);
                }   
            }
        }
    }
    

    测试结果如下:

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

    优化后的Actor模型

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

    请重新回看《三分钟掌握》 TransformBlock块的细节:

    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是单线程处理输入的消息, 此时我们设置了MaxDegreeOfParallelism参数,参数能在Actor中开启多线程并发执行,但是这里面就不能有共享变量(否则你又得加锁),恰好我们完成 (1) 迭代判断当前数字是不是素数这一步并不依赖共享对象,所以这一步性能与共享内存模型基本没差别。

    那为什么总体性能慢慢超过共享内存?

    这是因为执行第二步(2) 如果是素数,执行sum++, 共享内存要加解锁,线程上下文切换,而Actor单线程挨个处理, 总体就略胜共享内存模型了。

    这里再次强调,Actor模型执行第二步(2) 如果是素数,执行sum++,不可开启MaxDegreeOfParallelism,因为依赖了共享变量sum

    结束语

    请大家仔细对比结论和上图,脱离场景和硬件环境谈性能就是耍流氓,理解不同并发模型的风格和能力是关键,本文仅针对这个示例拍脑袋对比。

    实际要针对场景和未来的拓展性、可维护性、可操作性做技术选型 。

    That's All, 感谢.NET圈纪检委@懒得勤快促使我重温了单元测试的写法 & 深度分析Actor模型。


    本文来自博客园,作者:{有态度的马甲},转载请注明原文链接:https://www.cnblogs.com/JulianHuang/p/15088795.html

    欢迎关注我的原创高价值公众号

    上海鲜花港 - 郁金香
  • 相关阅读:
    MyBatis(第三方缓存整合原理&ehcache适配包下载)
    MyBatis(缓存机制)
    MyBatis(动态SQL2)
    MyBatis(动态SQL)
    MyBatis(映射文件中使用foreach标签时报错,属性collection的问题)
    MyBatis映射文件(编写SQL语句;可有可无(无的时候,使用注解编程))
    Python virtualenv with Sublime Text 3
    python & mongo问题记录
    CentOS On VirtualBox
    MySQL GROUP BY用法
  • 原文地址:https://www.cnblogs.com/JulianHuang/p/15088795.html
Copyright © 2011-2022 走看看