优势和劣势

使用Parallel.Invoke的优势就是使用它执行很多的方法很简单,而不用担心任务或者线程的问题。然而,它并不是适合所有的场景。Parallel.Invoke有很多的劣势

如果你使用它来启动那些需要执行很长时间的方法,它将会需要很长时间才能返回。这可能会导致很多的核心在很长时间都保持闲置。因此,使用这个方法的时候测量执行速度和逻辑核心使用率很重要。

它对并行的伸缩性有限制,因为它只能调用固定数目的委托。在前面的例子中,如果你在一个有16个核心的电脑上执行,它将只会并行启动四个方法。因此,12个逻辑核心仍然保持闲置。

每次使用这个方法执行并行方法之前都会增加额外的开销。

就像任何的并行代码,不同的方法之间存在内部依赖和难以控制的交互,会导致难以探测的并行bug和难以预料的副作用。然而,这个劣势是使用所有的并行代码,它并不是使用Parallel.Invoke是才存在的问题。

无法保证需要并行的方法的执行顺序;因此,Parallel.Invoke并不适合执行那些需要特定执行计划的复杂算法。

使用不同的并行执行计划启动的任何一个委托都可能抛出异常;因此,捕获和处理这些异常会比传统的串行代码更复杂。

交错并发和并发

正如你在前边例子和图片2-5中所看到的,交错并发和并发是不同的事情。

交错并发意味着在重叠的时间段内可以开始、执行和结束不同部分的代码。交错并发甚至可以运行在只有一个逻辑核心的电脑上。当很多的代码交错运行在只有一个逻辑核心的电脑上时,时间调度策略和快速的上下文切换提供了并行执行的假象。然而,使用这种硬件,交错执行这些代码比单独执行某一端独立的代码需要更多的时间,因为这些并发的代码是需要竞争硬件资源的。你可以讲交错并行想象成多辆卡车共享同一条车道。这也就是为什么交错并发也被定义为一种虚拟的并行。

并发意味着不同的代码可以同时执行,充分利用底层硬件的并发处理能力。真正的并发是不能发生在只有一个逻辑核心的计算机上。为了执行并行代码你至少需要两个逻辑核心。当真正的并发发生的时候是可以提升执行速度的,因为并行执行代码可以减少完成特定算法必须的时间开销。前边的图中提供了如下的可能的并发场景:

并发,四个逻辑核心的理想并行—在这种理想的情形下,四个方法的指令分别执行在一个不同的逻辑核心上。

结合交错并发和并发;并不完美的并行四个方法只能利用两个逻辑核心—有时,四个方法的执行并行运行在不同的逻辑核心上,有时它们必须等待它们的时间片。在这种情况下,交错并发结合了真正的并行。这是最普遍的情形,因为即使是在实时操作系统中,也确实很难实现完美的并行。

图 2-5

循序代码转化为并行代码

在过去十年,大多的C#编写的代码是顺序和同步执行的。因此,很多的算法的思想既没有并发也没有并行。大多的时间,你很难发现可以完整的转换成完全并行和完美伸缩性代码的方法。即使可以找到,但是这并不是最普遍的场景。

当你有并行代码并想利用潜在的并行来提升执行速度的时候,你必须找到可以并行的热点区域。然后,你可以将他们转化成并行代码,测试执行速度,确定潜在的伸缩性,并且确保在将现存顺序代码转换成并行代码的时候没有引入新的bug。

探测并行热点

列表2-3展示了一个例子,这是一个执行了如下两个顺序的方法的简单的控制台应用程序。

GenerateAESKeys—这个方法执行一个for循环,并根据指定的常量字段NUM_AES_KEYS产生相应数目的AES键。它使用System.Security.Cryptography.AesManaged类提供的GenerateKey方法。一旦产生了这个键,就会将这个byte数据转化成十六进制字符串形式,并将转换的结果保存在局部变量hexString里。

GenerateMD5Hashes—这个方法执行一个for循环,并使用md5算法根据给定的常量NUM_MD5_HASHES来产生相应数目的哈希值。它使用用户名作为参数调用System.Security.Cryptography.MD5类提供的ComputeHash方法。一旦产生了哈希值,就会将这个byte数组转换成十六进制的字符串形式,并使用局部变量hexString保存。


LISTING 2-3: Simple serial AES keys and MD5 hash generators
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
// Added for the Stopwatch
using System.Diagnostics;
// Added for the cryptography classes
using System.Security.Cryptography;
// This namespace will be used later to run code in parallel
using System.Threading.Tasks;
namespace Listing2_3
{
    class Program
    {
        private const int NUM_AES_KEYS = 800000;
        private const int NUM_MD5_HASHES = 100000;
        private static string ConvertToHexString(Byte[] byteArray)
        {
            // Convert the byte array to hexadecimal string
            var sb = new StringBuilder(byteArray.Length);
            for (int i = 0; i < byteArray.Length; i++)
            {
                sb.Append(byteArray[i].ToString("X2"));
            }
            return sb.ToString();
        }
        private static void GenerateAESKeys()
        {
            var sw = Stopwatch.StartNew();
            var aesM = new AesManaged();
            for (int i = 1; i <= NUM_AES_KEYS; i++)
            {
                aesM.GenerateKey();
                byte[] result = aesM.Key;
                string hexString = ConvertToHexString(result);
                // Console.WriteLine(“AES KEY: {0} “, hexString);
            }
            Debug.WriteLine("AES: " + sw.Elapsed.ToString());
        }
        private static void GenerateMD5Hashes()
        {
            var sw = Stopwatch.StartNew();
            var md5M = MD5.Create();
            for (int i = 1; i <= NUM_MD5_HASHES; i++)
            {
                byte[] data =
                Encoding.Unicode.GetBytes(
                Environment.UserName + i.ToString());
                byte[] result = md5M.ComputeHash(data);
                string hexString = ConvertToHexString(result);
                // Console.WriteLine(“MD5 HASH: {0}”, hexString);
            }
            Debug.WriteLine("MD5: " + sw.Elapsed.ToString());
        }
        static void Main(string[] args)
        {
            var sw = Stopwatch.StartNew();
            GenerateAESKeys();
            GenerateMD5Hashes();
            Debug.WriteLine(sw.Elapsed.ToString());
            // Display the results and wait for the user to press a key
            Console.ReadLine();
        }
    }
}

LISTING 2-3: Simple serial AES keys and MD5 hash generators
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
// Added for the Stopwatch
using System.Diagnostics;
// Added for the cryptography classes
using System.Security.Cryptography;
// This namespace will be used later to run code in parallel
using System.Threading.Tasks;
namespace Listing2_3
{
class Program
{
private const int NUM_AES_KEYS = 800000;
private const int NUM_MD5_HASHES = 100000;
private static string ConvertToHexString(Byte[] byteArray)
{
// Convert the byte array to hexadecimal string
var sb = new StringBuilder(byteArray.Length);
for (int i = 0; i < byteArray.Length; i++)
{
sb.Append(byteArray[i].ToString("X2"));
}
return sb.ToString();
}
private static void GenerateAESKeys()
{
var sw = Stopwatch.StartNew();
var aesM = new AesManaged();
for (int i = 1; i <= NUM_AES_KEYS; i++)
{
aesM.GenerateKey();
byte[] result = aesM.Key;
string hexString = ConvertToHexString(result);
// Console.WriteLine(“AES KEY: {0} “, hexString);
}
Debug.WriteLine("AES: " + sw.Elapsed.ToString());
}
private static void GenerateMD5Hashes()
{
var sw = Stopwatch.StartNew();
var md5M = MD5.Create();
for (int i = 1; i <= NUM_MD5_HASHES; i++)
{
byte[] data =
Encoding.Unicode.GetBytes(
Environment.UserName + i.ToString());
byte[] result = md5M.ComputeHash(data);
string hexString = ConvertToHexString(result);
// Console.WriteLine(“MD5 HASH: {0}”, hexString);
}
Debug.WriteLine("MD5: " + sw.Elapsed.ToString());
}
static void Main(string[] args)
{
var sw = Stopwatch.StartNew();
GenerateAESKeys();
GenerateMD5Hashes();
Debug.WriteLine(sw.Elapsed.ToString());
// Display the results and wait for the user to press a key
Console.ReadLine();
}
}
}

方法GenerateAESKeys中的for循环在代码里没有使用它的控制变量i,因为它仅仅控制产生一个随机AES key的次数。然而,在方法GenerateMD5Hashes中将它的控制变量i添加到计算机用户名后边。然后,使用这个字符串作为调用产生哈希值的方法的输入数据,具体的代码如下代码所示


for (int i = 1; i <= NUM_MD5_HASHES; i++)
{
  byte[] data = Encoding.Unicode.GetBytes(Environment.UserName + i.ToString());
  byte[] result = md5M.ComputeHash(data);
  string hexString = ConvertToHexString(result);
  // Console.WriteLine(hexString);
}

for (int i = 1; i <= NUM_MD5_HASHES; i++)
{
byte[] data = Encoding.Unicode.GetBytes(Environment.UserName + i.ToString());
byte[] result = md5M.ComputeHash(data);
string hexString = ConvertToHexString(result);
// Console.WriteLine(hexString);
}

清单2-3中的高亮代码行是测量每个方法执行的总时间。它通过在每个方法开始的时候调用StartNew方法来开始一个新的StopWatch,然后最终将消耗的时间写到Debug的输出中。

清单2-3中也展示了那些被注释掉的输出产生的key和哈希值的语句,因为这些操作会将字符串发送到控制台,这将会产生性能瓶颈影响时间测试的准确性。

图2-6展示了这个程序的顺序执行流程和在一个有双核微处理器的计算机上执行前边两个方法所分别使用的时间。

两个方法需要执行将近14秒。第一个方法执行8s,后一个需要6s。当然,这些消耗的时间会随底层的硬件配置产生很大的变化。这两个方法之间没有进行任何的交互;因此,他们之间是完全的相互独立的。就这样一个接着一个的顺序执行,是不能利用令外一个核心提供的并行处理能力的。因此,这两个方法就是一个并行的热区,在这里并行可以帮助我们完成一个重大的执行速度的提升。例如,你可以使用Parallel.Invoke并行的执行这两个方法。

图 2-6

测量并行的执行速度提升

使用下边的新版本替换列表2-3中的Main方法,这里使用Parallel.Invoke来并行启动两个方法。


static void Main(string[] args)
{
    var sw = Stopwatch.StartNew();
    Parallel.Invoke(
        () => GenerateAESKeys(),
        () => GenerateMD5Hashes());
    Debug.WriteLine(sw.Elapsed.ToString());
    // Display the results and wait for the user to press a key
    Console.WriteLine(“Finished!”);
    Console.ReadLine();
}

static void Main(string[] args)
{
var sw = Stopwatch.StartNew();
Parallel.Invoke(
() => GenerateAESKeys(),
() => GenerateMD5Hashes());
Debug.WriteLine(sw.Elapsed.ToString());
// Display the results and wait for the user to press a key
Console.WriteLine(“Finished!”);
Console.ReadLine();
}

图2-7展示了这个程序的新版本的并行执行流程和两个方法在使用双核微处理器的计算机上执行消耗的时间。

现在两个方法执行将近9m,因为他们利用了微处理器提供的两个核心。因此,你可以使用下边的公式计算其可以实现的速度提升:

Speedup = (Serial execution time) / (Parallel execution time)

14 / 9 = 1.56x

图 2-7

正如你所看到的,GenerateAESKeys比GenerateMD5Hashes消耗了更长的时间:9:6。然而,如果所有的委托没有都执行结束,Parallel.Invoke是不会执行下边的代码的。因此,最后的3s,应用程序并没有利用每一个核心,存在一个load-imbalance的问题,如图2-8所示。

图 2-8

如果这个应用程序运行在一个有四核微处理器的计算机上,它的速度提升几乎是一样的,因为它不能调度使用底层硬件的另外两个核心。

在这个例子中,你检测并行的热点,并添加一些代码来测试执行特定方法消耗的时间。然后,你可以仅仅改变几行代码完成一个有趣的执行速度提升。当在数据并行场景中可用核心的数目增加时,你现在需要知道命令数据并行TPL结构来实现一个更好的结果并改善伸缩性。

理解并行执行

接下来,你需要解除在方法GenerateMD5Hashes和GenerateAESKeys中注释的有关向控制台输出的代码行:

Console.WriteLine(“AESKEY: {0} “, hexString);

Console.WriteLine(“MD5HASH: {0}”, hexString);

向控制台输出对并行执行会产生性能瓶颈。然而,这次,现在不需要测试准确的时间。相反,你也可以看到并行执行的两个方法的输出结果。列表2-4展示了这个程序产生的一个控制台输出的例子。列表中高亮的短十六进制字符串是相应的MD5哈希。其他的十六进制字符串展示的是AES key。每一个AES key消耗的时间比每个md5哈希要短。记住那段代码产生了800000 AES key和100000 MD5哈希。

List2-4

现在,注释掉这两个方法中那些输出结果到控制台的代码。

使用Parallel.Invoke并行你的代码的更多相关文章

  1. Parallel.Invoke并行你的代码

    Parallel.Invoke并行你的代码 使用Parallel.Invoke并行你的代码 优势和劣势 使用Parallel.Invoke的优势就是使用它执行很多的方法很简单,而不用担心任务或者线程的 ...

  2. Parallel.Invoke 并行的使用

    Parallel类  在System.Threading.Tasks 命名空间下 下面有几个方法,这里讲一下Invoke的用法 下面我们定义几个方法方便测试 先自定义Response 防止并行的时候占 ...

  3. C#并行编程--命令式数据并行(Parallel.Invoke)---与匿名函数一起理解(转载整理)

    命令式数据并行   Visual C# 2010和.NETFramework4.0提供了很多令人激动的新特性,这些特性是为应对多核处理器和多处理器的复杂性设计的.然而,因为他们包括了完整的新的特性,开 ...

  4. C#并行编程--命令式数据并行(Parallel.Invoke)

    命令式数据并行   Visual C# 2010和.NETFramework4.0提供了很多令人激动的新特性,这些特性是为应对多核处理器和多处理器的复杂性设计的.然而,因为他们包括了完整的新的特性,开 ...

  5. C#并行编程中的Parallel.Invoke

    一.基础知识 并行编程:并行编程是指软件开发的代码,它能在同一时间执行多个计算任务,提高执行效率和性能一种编程方式,属于多线程编程范畴.所以我们在设计过程中一般会将很多任务划分成若干个互相独立子任务, ...

  6. C#5.0之后推荐使用TPL(Task Parallel Libray 任务并行库) 和PLINQ(Parallel LINQ, 并行Linq). 其次是TAP(Task-based Asynchronous Pattern, 基于任务的异步模式)

    学习书籍: <C#本质论> 1--C#5.0之后推荐使用TPL(Task Parallel Libray 任务并行库) 和PLINQ(Parallel LINQ, 并行Linq). 其次是 ...

  7. C# Parallel.Invoke 实现

    Parallel.Invoke应该是Parallel几个方法中最简单的一个了,我们来看看它的实现,为了方法大家理解,我尽量保留源码中的注释: public static class Parallel ...

  8. C#异步编程のParallel(并行)

    Parallel是循环中开启多线程 Stopwatch watch1 = new Stopwatch(); watch1.Start(); for (int i = 1; i <= 10; i+ ...

  9. Parallel.ForEach() 并行循环

    现在的电脑几乎都是多核的,但在软件中并还没有跟上这个节奏,大多数软件还是采用传统的方式,并没有很好的发挥多核的优势. 微软的并行运算平台(Microsoft’s Parallel Computing ...

随机推荐

  1. 初学hibernate的心得体会

    在初步学习了hibernate之后,使我明白了hibernate是一个怎样的软件.hibernate是一个比较独立的框架,它不需要太多其他软件的支持.hibernate是一个开放源代码的关系映射框架, ...

  2. 【ASP.Net Core】不编译视图文件

    原文:[ASP.Net Core]不编译视图文件 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/aqtata/article/details/818 ...

  3. vue中解决拖动和点击事件的冲突

    BUG说明: 鼠标上下方向拖拽,如果松开时鼠标位于悬浮按钮上会默认执行click事件,经验证,click事件与mouse事件的执行顺序为onmousedown =>onmouseup => ...

  4. pop&dismiss

    //dismiss到根视图 - (void)dismissToRootViewController{ UIViewController *vc = self; while (vc.presenting ...

  5. 2018-8-10-WPF-使用-Direct2D1-画图-绘制基本图形

    title author date CreateTime categories WPF 使用 Direct2D1 画图 绘制基本图形 lindexi 2018-08-10 19:16:53 +0800 ...

  6. ionic js ion-tabs选项卡栏操作

    ionic 选项卡栏操作 ion-tabs ion-tabs 是有一组页面选项卡组成的选项卡栏.可以通过点击选项来切换页面. 对于 iOS,它会出现在屏幕的底部,Android会出现在屏幕的顶部(导航 ...

  7. Delphi屏幕截图的实现

    首先要获得设备环境的句柄,可以通过GetDC函数来获得,对于这个函数,MSDN上是这样说明的 The GetDC function retrieves a handle to a device con ...

  8. csp-s模拟测试90

    csp-s模拟测试90 考场发明$Spfa$祭. $T1$按照题意模拟,然后我就发现我死了.一气之下删掉了$priority$,拍了几下发现贼jb快而且还是对的就开心地交了.$T2$的差分状态定义很棒 ...

  9. VS2010-MFC(对话框:消息对话框)

    转自:http://www.jizhuomi.com/software/171.html 前面几节讲了属性页对话框,我们可以根据所讲内容方便的建立自己的属性页对话框.本节讲解Windows系统中最常用 ...

  10. CentOS升级GDB

    需要先安装ncurses-devel和texinfo 如果不安装ncurses-devel,编译会出错 如果不安装texinfo或者版本太低,安装会出错 1.yum install -y ncurse ...