Parallel.Invoke并行你的代码

使用Parallel.Invoke并行你的代码

优势和劣势

使用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保存。

  1. LISTING 2-3: Simple serial AES keys and MD5 hash generators
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Text;
  6. // Added for the Stopwatch
  7. using System.Diagnostics;
  8. // Added for the cryptography classes
  9. using System.Security.Cryptography;
  10. // This namespace will be used later to run code in parallel
  11. using System.Threading.Tasks;
  12. namespace Listing2_3
  13. {
  14. class Program
  15. {
  16. private const int NUM_AES_KEYS = 800000;
  17. private const int NUM_MD5_HASHES = 100000;
  18. private static string ConvertToHexString(Byte[] byteArray)
  19. {
  20. // Convert the byte array to hexadecimal string
  21. var sb = new StringBuilder(byteArray.Length);
  22. for (int i = 0; i < byteArray.Length; i++)
  23. {
  24. sb.Append(byteArray[i].ToString("X2"));
  25. }
  26. return sb.ToString();
  27. }
  28. private static void GenerateAESKeys()
  29. {
  30. var sw = Stopwatch.StartNew();
  31. var aesM = new AesManaged();
  32. for (int i = 1; i <= NUM_AES_KEYS; i++)
  33. {
  34. aesM.GenerateKey();
  35. byte[] result = aesM.Key;
  36. string hexString = ConvertToHexString(result);
  37. // Console.WriteLine(“AES KEY: {0} “, hexString);
  38. }
  39. Debug.WriteLine("AES: " + sw.Elapsed.ToString());
  40. }
  41. private static void GenerateMD5Hashes()
  42. {
  43. var sw = Stopwatch.StartNew();
  44. var md5M = MD5.Create();
  45. for (int i = 1; i <= NUM_MD5_HASHES; i++)
  46. {
  47. byte[] data =
  48. Encoding.Unicode.GetBytes(
  49. Environment.UserName + i.ToString());
  50. byte[] result = md5M.ComputeHash(data);
  51. string hexString = ConvertToHexString(result);
  52. // Console.WriteLine(“MD5 HASH: {0}”, hexString);
  53. }
  54. Debug.WriteLine("MD5: " + sw.Elapsed.ToString());
  55. }
  56. static void Main(string[] args)
  57. {
  58. var sw = Stopwatch.StartNew();
  59. GenerateAESKeys();
  60. GenerateMD5Hashes();
  61. Debug.WriteLine(sw.Elapsed.ToString());
  62. // Display the results and wait for the user to press a key
  63. Console.ReadLine();
  64. }
  65. }
  66. }

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

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

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

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

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

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

图 2-6

测量并行的执行速度提升

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

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

图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有很多的劣势 如果你使用它 ...

  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. Web API在OWIN下实现OAuth

    OAuth(Open Authorization) 为用户资源的授权提供了一个安全的.开放而又简易的标准.与以往的授权方式不同之处是OAuth的授权不会使第三方触及到用户的帐号信息(如用户名与密码), ...

  2. 7 个顶级的 HTML5 Canvas 动画赏析

    HTML5确实是一项改革浏览器乃至整个软件行业的新技术,它可以帮助我们Web开发者很方便地在网页上实现动画特效,而无需臃肿的Flash作为支撑.本文分享7个顶级的HTML5 Canvas 动画,都有非 ...

  3. Linux账号密码过期会导致crontab作业不能执行

    今天一同事报告Linux服务器上的crontab作业没有运行,检查/var/log/cron日志后发现下面错误信息 Jan 19 16:30:01 xxxx crond[31399]: Authent ...

  4. 编写Java应用程序,定义Animal类,此类中有动物的属性:名称 name,腿的数量legs,统计动物的数量 count;方法:设置动物腿数量的方法 void setLegs(),获得腿数量的方法 getLegs(),设置动物名称的方法 setKind(),获得动物名称的方法 getKind(),获得动物数量的方法 getCount()。定义Fish类,是Animal类的子类,统计鱼的数量 co

    package com.hanqi.test; public class Animal { private String name; private int legs; private int cou ...

  5. 好用的排名函数~ROW_NUMBER(),RANK(),DENSE_RANK() 三兄弟

    排名函数三兄弟,一看名字就知道,都是为了排名而生!但是各自有各自的特色!以下一个例子说明问题!(以下栗子没有使用Partition By 的关键字,整个结果集进行排序) RANK 每个值一个排名,同样 ...

  6. mysql 远程访问控制

    如需要让192.168.2.3的test用户可以访问本机所有数据库,mysql命令如下 mysql>GRANT ALL PRIVILEGES ON *.* TO 'test'@'192.168. ...

  7. Linux设置环境变量小结:设置永久变量&临时变量 全局变量&局部变量

    1.总结背景 在linux系统下,如果你下载并安装了应用程序,很有可能在键入它的名称时出现“command not found”的提示内容.如果每次都到安装目标文件夹内,找到可执行文件来进行操作就太繁 ...

  8. 双核CPU,跑程序会报rcu_sched_state detected stalls on CPUs/tasks 错误

    有一份SDK,之前跑在PPC405EX上没问题。最近换平台,CPU使用了PowerPC的P1020,双核。linux版本也升级到了3.0.48版本。升级之后出现了一个问题:SDK里面的程序跑一段时间之 ...

  9. 定时器的应用---查询方式---让8个LED灯,左右各4个来回亮

    定时器的应用,查询方式.让8个LED灯,左右各4个来回亮 代码: /********************** 查询方式是主程序不断的查询是否中断,而不需要准备子程序 *************** ...

  10. java23种设计模式

    http://www.cnblogs.com/beijiguangyong/archive/2010/11/15/2302807.html#_Toc281750445 http://www.runoo ...