优势和劣势

使用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. linux最常用vim命令记录

    先来一张图了解3种模式: 一.输入vim 命令,进入命令模式 此时可执行: 1.行号显示 (1):set nu    显示文本的行号:    :nonu 取消显示行号 2.光标移动 (1)n<E ...

  2. springboot + ajax + mybatis 实现批量删除

    实现思路: 1. checkbox全选获取批量删除的id数组 2. ajax以字符串的形式将id数组传给控制器 3. 控制器将字符串分割成List数组作为参数传给mapper 具体代码: 1. 前端代 ...

  3. uoj213 【UNR #1】争夺圣杯

    题目 设\(f_i\)表示所有长度为\(i\)的区间的最大值的和,求\(\bigoplus \sum_{i=1}^nf_i\) 不难发现随机数据非常好做 由于一个随机序列的前缀最大值期望只会变化\(\ ...

  4. python自动化基础(参数化)

    一.创建加法类 #定义一个数学加法类 class Mathmethod(): def add(self,a,b): return(a+b) def sub(self,a,b): return(a-b) ...

  5. MySQL 11章_索引、触发器

    一. 索引: . 为什么要使用索引: 一本书需要目录能快速定位到寻找的内容,同理,数据表中的数据很多时候也可以为他们创建相应的“目录”,称为索引,当创建索引后查询数据也会更加高效 . Mysql中的索 ...

  6. 找到你的位置(JS在页面中的位置)最常用的方式是在页面中head部分放置<script>元素,浏览器解析head部分就会执行这个代码,然后才解析页面的其余部分

    找到你的位置(JS在页面中的位置) 我们可以将JavaScript代码放在html文件中任何位置,但是我们一般放在网页的head或者body部分. 放在<head>部分 最常用的方式是在页 ...

  7. redis通过命令传参以及主从复制

    config set appendonly  yes  临时生效 如果想永久生效的话,执行 config rewrite 并不是所有的都支持修改 config set bind ip  修改bind参 ...

  8. 获取linux性能数据

    import reimport osimport sysimport jsonimport socketfrom urllib import request,parseimport urllibfro ...

  9. codeforces 1180E Serge and Dining Room 线段树

    题目传送门 题目大意: 给出a序列和b序列,a序列为各种食物的价格,b序列为一列排着队的小朋友拥有的钱,小朋友依次购买食物,每个人都买自己能买的起的最贵的食物,买不起就离开队伍.给出q次操作,操作1是 ...

  10. drupal7 代码生成用户,并自动登录

    直接上代码 1. 生成用户(注册) $edit = [ "name" => "name", "pass" => "pa ...