14.2.5 并行化应用程序

来源:互联网 发布:学摄影的软件 编辑:程序博客网 时间:2024/05/20 01:38

14.2.5 并行化应用程序


 


    因为这一章是真正有关并行化的,这才是这个应用程序中的最有趣的部分。我们将讨论两种语言写的代码,首先以可能最简单的方法实现 C# 版本。


 


在 C# 中以并行方式运行滤镜


 


    要实现 C# 版本,我们从清单 14.11 中把 RunFilter 方法拿过来,把 for 循环替换成对 Parallel.For 方法的调用。由于 C# 3.0 中有 lambda 函数,它正是句法结构的转换(syntactic transformation)。我们还要写一个 MakeEffect 方法的并行变体,来自清单 14.14,返回图形效果(是一个函数),执行这个颜色滤镜。你可以在清单 14.17 中看到并行版本。


 


Listing 14.17 Applying color filter in parallel (C#)


 


public static SimpleColor[,] RunFilterParallel
    (this SimpleColor[,] arr, Func<SimpleColor, SimpleColor> f) {
  int height = arr.GetLength(0), width = arr.GetLength(1);
  var result = new SimpleColor[height, width];
  Parallel.For(0, height, y => {
    for(int x = 0; x < width; x++)
      result[y, x] = f(arr[y, x]);
  });
return result;
}


public static Func<SimpleColor[,], SimpleColor[,]> MakeParallelEffect
    (Func<SimpleColor, SimpleColor> filter) {
  return arr => RunFilterParallel(arr, filter);
}


 


    原来的代码包含两个嵌套的 for 循环,但我们只并行化外部的循环。对于大多数图像来说,这会给基础库足够的灵活性,高效地并行化代码,而无需创建大量不必要的任务。使滤镜以并行运行,只要改变两行代码。把 for 循环改成对  Parallel.For 方法的调用,并不总是像看上去那么简单。一定要仔细看看代码,并考虑并行化是否会导致任何问题。


    例如,我们必须小心循环是否会修改任何可变的状态。在清单 14.17 中,我们通过只使用本地可变状态,避免这一问题。数组 result 不能从这个函数的外部访问,使整体的方法是函数式的。此外,每次迭代只使用数组的单独部分(一个水平行)。


    另外,很多 .NET 类并不是线程安全(thread-safe)的,这意味着,当你开始从多个线程访问单个实例时,它们的行为可能是未定义的。在 14.3 节中,我们会看到这确实是一个问题,甚至看起来简单的类型,比如,Random;我们还将学习如何使用锁,来解决这个问题。首先,让我们看看上面代码的 F# 版本。


 


F# 中数组的并行处理


 


    F# 版本的源代码将几乎就是直接翻译我们所看到的前面的 C# 的清单,但同时,它是更为一般的函数。如果你在 F# 中实现前面 C# 清单,其中的变化是,你可能会删除所有不必要的类型批注。执行这些操作之后, 可以看到,代码没有在任何地方显式提到 SimpleColor 类型,它不需要知道正在处理颜色。在 Visual Studio 中,如果你悬停在从 C# 翻译过来的函数上,会看到以下推断出的类型:


 


('a -> 'b) -> 'a[,] -> 'b[,]


 


    只是通过删除了类型注释,我们就使得函数更加泛型。这个函数的类型与 Array2D.map 类型是相同的,在这一章的前面,我们曾经用过。类型签名中的变化也提示,名字也应该是泛型化的。毕竟,我们将执行映射操作,只是以并行方式。这些改变的结果展示在清单 14.18 中。


 


Listing 14.18 Parallel map function for the 2D array (F#)


 


module Array2D =
  module Parallel =
    let map f (arr:_ [,]) =
      let height, width = arr.GetLength(0), arr.GetLength(1)
      let result = Array2D.zeroCreate height width
      pfor 0 (height - 1) (fun y –>
        for x = 0 to width - 1 do
          result.[y, x] <- f(arr.[y, x]))
      result


let runFilterParallel f arr = Array2D.Parallel.map f arr




    简单的翻译行为揭示了一个更深层次的方面,我们的原来的代码是很奇怪的现象。新的函数所做的事与 Array2D.map 完全相同,除了是以并行方式执行以外,所以,我们把函数命名这 map,并把它放在模块 Array2D.Parallel 的内部,使它更可重复使用的。要实现并行化,我们将使用我们的工具函数 pfor,来自 14.1.1 节。


    我们已经注意到 F# 中的这种泛型化之后,就可以改变 C# 版本来匹配它,像这样改变方法的声明:


 


public static TResult[,] ParallelMap<TSource, TResult>
    (this TSource[,] arr, Func<TSource, TResult> f) {


 


    然后,我们必须适当地通过代码,传播类型参数,用 SimpleColor 代替 TSource 或 TResult,取决于上下文。类型推断然后会处理提供的类型参数值,在这里,我们会调用该方法。


    清单 14.18 的最后一行,为并行的 map 函数创建别名。我们原来的目标是写一个函数,来并行运行图形滤镜,别名使代码更具可读性,因为,名字提供一个更好的线索,说明函数是如何使用的。


    现在,我们已经对简单的颜色滤镜进行了并行化,下面要去实现一个更一般的效果:模糊化图像。这会把我们的应用程序所覆盖的范围起来,但是,你可能要尝试更多的效果。