【问题标题】:Which amount of code execution should I parallelize?我应该并行化多少代码执行?
【发布时间】:2010-02-06 20:57:57
【问题描述】:

如果我想并行执行算法,我应该拆分哪些小代码块?

一个经典的例子是排序算法。对于什么元素大小或典型执行时间,在多个线程之间拆分排序是否有意义?或者什么时候等待另一个线程的开销大于单个线程的执行时间?

有什么简单的规则吗?这取决于操作系统吗?

【问题讨论】:

  • 很难回答的问题 - 实际上取决于问题域和硬件限制,即排序 - 您要排序多少项目以及使用什么硬件

标签: multithreading concurrency parallel-processing


【解决方案1】:

关键规则是“仅当分叉开销远小于分叉将完成的工作量时才分叉”。由于分叉开销是您使用的特定技术的属性,完成这项工作的努力也是如此,因此您在某种意义上必须凭经验确定这一点。您最终可能会在代码中使用一些阈值调整常量来表示这种权衡。

在实践中您会发现,找到可分离的工作块实际上很困难。如果你把工作块做得很小,它就没有很多依赖关系,一旦所有输入数据流都准备好,你就可以安排它。但是小块通常意味着小工作,并且分叉开销通常会抵消收益。如果你试图让这些块变大,它们有太多的依赖关系,你不能把它们分开来安排它们。

有些人很幸运,能找到这么大的块;我们称这些人中的大多数为物理学家和/或 Fortran 程序员,他们正在利用通过将世界划分为尽可能多的小块而产生的数据并行性。

我所知道的唯一不错的解决方法是使用非常快速的分叉机制,这样你就可以找到最小的实用块。不幸的是,提供这样做的并行库是......动态调用的库,具有相应的动态调用开销。包含并行原语的典型库需要 100 到数千个周期才能实现“分叉”;如果您的工作量是 100 条机器指令,这将是个坏消息。

我坚信要获得如此快速的分叉机制,语言编译器必须知道您正在执行分叉,例如,“fork”(无论拼写如何 :-) 是该语言中的关键字。然后编译器可以看到分叉,并预先分配所需的一切以最小化完成此操作的时间,并生成特殊代码来管理分叉(和连接)步骤。

我设计的PARLANSE 语言,我们在 Semantic Designs 使用的语言就是这样一种语言。 它是一种语法上类似于 Lisp 的语言(但不是语义上的)。它的并行运算符拼写为“(|| ... )”。您可以在下面我们每天使用的快速排序模块中看到它。 您还可以查看根据经验确定的显式 QuickSortParallelThreshold 值。 此 Quicksort 在 Intel x86 系统上线性扩展至 8 个内核。

(define QuickSort
  (module
    (;; (define Value nu)
        (compileifthen (~ (defined QuickSortWithParlanseBuiltInOrderingOfNu))
          (define QuickSortWithParlanseBuiltInOrderingOfNu ~f) ; use PARLANSE comparison operators
        )compileifthen
        (compileifthen (~ (defined QuickSortParallelThreshold))
          (define QuickSortParallelThreshold 100)
        )compileifthen
        (compileifthen (~ (defined QuickSortThreshold))
          (compileifthenelse QuickSortWithParlanseBuiltInOrderingOfNu
            (define QuickSortThreshold 16)
            (define QuickSortThreshold 8)
          )compileifthenelse
        )compileifthen
        (compileifthenelse (~ (defined QuickSortWithCompareByReference))
          (define QuickSortWithCompareByReference ~f)
          (compileifthen QuickSortWithParlanseBuiltInOrderingOfNu
            (define QuickSortWithCompareByReference ~f)
          )compileifthen
        )compileifthenelse
        (define SortRange
          (action (procedure (structure (compileifthen (~ QuickSortWithParlanseBuiltInOrderingOfNu)
                                          (compileifthenelse (~ QuickSortWithCompareByReference)
                                            [compare (function (sort integer (range -1 +1)) (structure [value1 Value] [value2 Value]))]
                                            [compare (function (sort integer (range -1 +1)) (structure [value1 (reference Value)] [value2 (reference Value)]))]
                                          )compileifthenelse
                                        )compileifthen
                                        [a (reference (array Value 1 dynamic))]
                                        [from natural]
                                        [to natural]
                             )structure
                  )procedure
            (local (;; (define quicksort
                         (action (procedure (structure [l integer] [r integer])))
                       )define

                       (define quicksort
                         (action (procedure (structure [l integer] [r integer]))
                           (ifthenelse (<= (- r l) (coerce integer QuickSortThreshold))
                             (do [i integer] (++ l) r +1
                               (local (= [exch Value] a:i)
                                 (block exit_if_inserted
                                   (;; (do [j integer] (-- i) l -1
                                         (ifthenelse (compileifthenelse QuickSortWithParlanseBuiltInOrderingOfNu
                                                       (> a:j exch)
                                                       (compileifthenelse (~ QuickSortWithCompareByReference)
                                                         (== (compare a:j exch) +1)
                                                         (== (compare (. a:j) (. exch)) +1)
                                                       )compileifthenelse
                                                     )compileifthenelse
                                           (= a:(++ j) a:j)
                                           (;; (= a:(++ j) exch)
                                               (exitblock exit_if_inserted)
                                           );;
                                         )ifthenelse
                                       )do
                                       (= a:l exch)
                                   );;
                                 )block
                               )local
                             )do
                             (local (;; (= [i integer] l)
                                        (= [j integer] r)
                                        (= [p integer] l)
                                        (= [q integer] r)
                                        [exch Value]
                                    );;
                               (;;
                                  `use middle element as pivot':
                                    (local (= [m integer] (// (+ l r) +2))
                                      (;; (= exch a:m)
                                          (= a:m a:r)
                                          (= a:r exch)
                                      );;
                                    )local
                                  `4-way partitioning = < > =':
                                    (loop exit_if_partitioned
                                      (;;
                                         `find element greater than pivot':
                                           (loop exit_if_greater_than_found
                                             (;; (compileifthenelse QuickSortWithParlanseBuiltInOrderingOfNu
                                                   (ifthenelse (< a:i a:r)
                                                     (consume ~t)
                                                     (ifthenelse (> a:i a:r)
                                                       (exitblock exit_if_greater_than_found)
                                                       (;; (ifthen (>= i j)
                                                             (exitblock exit_if_partitioned)
                                                           )ifthen
                                                           (= exch a:p)
                                                           (= a:p a:i)
                                                           (= a:i exch)
                                                           (+= p 1)
                                                       );;
                                                     )ifthenelse
                                                   )ifthenelse
                                                   (case (compileifthenelse (~ QuickSortWithCompareByReference)
                                                           (compare a:i a:r)
                                                           (compare (. a:i) (. a:r))
                                                         )compileifthenelse
                                                     -1
                                                       (consume ~t)
                                                     +1
                                                       (exitblock exit_if_greater_than_found)
                                                     else (;; (ifthen (>= i j)
                                                                (exitblock exit_if_partitioned)
                                                              )ifthen
                                                              (= exch a:p)
                                                              (= a:p a:i)
                                                              (= a:i exch)
                                                              (+= p 1)
                                                          );;
                                                   )case
                                                 )compileifthenelse
                                                 (+= i 1)
                                             );;
                                           )loop
                                         `find element less than to pivot':
                                           (loop exit_if_less_than_found
                                             (;; (-= j 1)
                                                 (ifthen (>= i j)
                                                   (exitblock exit_if_partitioned)
                                                 )ifthen
                                                 (compileifthenelse QuickSortWithParlanseBuiltInOrderingOfNu
                                                   (ifthenelse (< a:j a:r)
                                                     (exitblock exit_if_less_than_found)
                                                     (ifthenelse (> a:j a:r)
                                                       (consume ~t)
                                                       (;; (-= q 1)
                                                           (= exch a:j)
                                                           (= a:j a:q)
                                                           (= a:q exch)
                                                       );;
                                                     )ifthenelse
                                                   )ifthenelse
                                                   (case (compileifthenelse (~ QuickSortWithCompareByReference)
                                                           (compare a:j a:r)
                                                           (compare (. a:j) (. a:r))
                                                         )compileifthenelse
                                                     -1
                                                       (exitblock exit_if_less_than_found)
                                                     +1
                                                       (consume ~t)
                                                     else (;; (-= q 1)
                                                              (= exch a:j)
                                                              (= a:j a:q)
                                                              (= a:q exch)
                                                          );;
                                                   )case
                                                 )compileifthenelse
                                             );;
                                           )loop
                                         `move found elements to proper partitions':
                                           (;; (= exch a:i)
                                               (= a:i a:j)
                                               (= a:j exch)
                                           );;
                                         `increment index':
                                           (+= i 1)
                                      );;
                                    )loop
                                  `3-way partitioning < = >':
                                    (;;
                                       `move pivot to final location':
                                         (;; (= exch a:i)
                                             (= a:i a:r)
                                             (= a:r exch)
                                             (= j (-- i))
                                             (= i (++ i))
                                         );;
                                       `move elements equal to pivot to final locations':
                                         (;; (do [k integer] l (-- p) +1
                                               (;; (= exch a:k)
                                                   (= a:k a:j)
                                                   (= a:j exch)
                                                   (-= j 1)
                                               );;
                                             )do
                                             (do [k integer] (-- r) q -1
                                               (;; (= exch a:i)
                                                   (= a:i a:k)
                                                   (= a:k exch)
                                                   (+= i 1)
                                               );;
                                             )do
                                         );;
                                    );;
                                  `sort partitions not equal to pivot':
                                    (ifthenelse (<= (- r l) (coerce integer QuickSortParallelThreshold))
                                      (;; (quicksort l j)
                                          (quicksort i r)
                                      );;
                                      (|| (quicksort l j)
                                          (quicksort i r)
                                      )||
                                    )ifthenelse
                               );;
                             )local
                           )ifthenelse
                         )action
                       )define

                   );;
              (;; (quicksort (coerce integer from) (coerce integer to))
                  (ifdebug (do [i integer] (coerce integer from) (-- (coerce integer to)) +1
                             (trust (compileifthenelse QuickSortWithParlanseBuiltInOrderingOfNu
                                      (<= a:i a:(++ i))
                                      (compileifthenelse (~ QuickSortWithCompareByReference)
                                        (<= (compare a:i a:(++ i)) +0)
                                        (<= (compare (. a:i) (. a:(++ i))) +0)
                                      )compileifthenelse
                                    )compileifthenelse
                                    `QuickSort:Sort -> The array is not sorted.'
                             )trust
                           )do
                  )ifdebug
              );;
            )local
          )action
        )define

        (define Sort
          (action (procedure (structure (compileifthen (~ QuickSortWithParlanseBuiltInOrderingOfNu)
                                          (compileifthenelse (~ QuickSortWithCompareByReference)
                                            [compare (function (sort integer (range -1 +1)) (structure [value1 Value] [value2 Value]))]
                                            [compare (function (sort integer (range -1 +1)) (structure [value1 (reference Value)] [value2 (reference Value)]))]
                                          )compileifthenelse
                                        )compileifthen
                                        [a (reference (array Value 1 dynamic))]
                             )structure
                  )procedure
            (compileifthenelse (~ QuickSortWithParlanseBuiltInOrderingOfNu)
              (SortRange compare a (coerce natural (lowerbound (@ a) 1)) (coerce natural (upperbound (@ a) 1)))
              (SortRange a (coerce natural (lowerbound (@ a) 1)) (coerce natural (upperbound (@ a) 1)))
            )compileifthenelse
          )action
        )define

    );;
  )module
)define

【讨论】:

    【解决方案2】:

    这取决于线程间通信的开销。我用图像处理测试了openMP,有一行像素很方便,也提供了很好的加速。我的图像是百万像素,所以有 1000 个任务,这可能足以让今天的多核机器保持忙碌。您也不需要将自己限制在耗时超过一秒左右的工作上。在此示例中,作业的加速时间为 10 毫秒,并且清晰可见。

    现在这是一个令人愉快的算法,因为它不是递归的,因此一个任务对另一个任务没有依赖关系,并且所有任务都自动具有相同的大小。

    由于任务大小不同,排序算法会更难。您希望能够对此进行试验,并可能选择一种更容易并行化的类型。

    【讨论】:

      【解决方案3】:

      参加几门并发和并行编程课程。学习几种技术,例如普通的 fork & forget 或“手动”多线程(Java 线程或 pthread)、MPI、OpenMP、BSP,甚至可能是 CUDA 或 OpenCL。然后要么决定成为专家,要么让专家设计和实现高效且正确的并行算法。 “并行”部分很容易,而“有效”和“正确”部分则不是,当两者都需要时。即使是由专家设计和实现的 Java 并发向量集合,在第一个版本中也存在缺陷。在 Java 标准的第一版中,内存模型的单纯定义并不明确!

      最简单的规则:使用现成的组件由专家设计和实现,不要试图同时实现正确性和效率来设计自己的并行算法,除非你是专家。

      【讨论】:

        【解决方案4】:

        以编程方式解决此问题是并行计算的圣杯之一,并且有许多库可以针对特定问题(例如,Data Parallel Haskell)逼近最佳并行度。

        无论如何,要手动执行此操作,您需要了解:

        • 您希望并行化的算法(是否可并行化?)
        • 数据的特征,例如大小、位置(在磁盘上、在内存中)等。
        • 您正在运行的硬件,例如核心数、内存延迟、缓存大小/行/关联性等。
        • 实现语言(协程、绿色线程、操作系统线程)和操作系统的线程模型。
        • 线程间产生和上下文切换的成本。

        假设算法是可并行的,你的目标是找到线程的数量和数据的相对块大小,这样你就可以优化使用硬件来生成解决方案。

        如果没有大量的实验,这很难做到。我首选的解决方法是运行大量基准测试,并根据以下一种或多种组合获取性能数据:

        • 线程数。
        • 缓冲区大小(如果数据不在 RAM 中)以某个合理的值递增(例如,块大小、数据包大小、缓存大小等)
        • 不同的块大小(如果您可以增量处理数据)。
        • 用于操作系统或语言运行时的各种调整旋钮。
        • 将线程固定到 CPU 以改善局部性。

        无论如何,这不是一件容易的事,并且有一些工具和库可以帮助您从可并行化的问题中获得尽可能多的性能。只有充分了解您的数据、代码和运行时环境,才能正确执行此操作。

        【讨论】:

          猜你喜欢
          • 2012-06-11
          • 1970-01-01
          • 2011-06-15
          • 2021-07-17
          • 2021-04-27
          • 2021-08-10
          • 1970-01-01
          • 2015-01-27
          • 2017-08-20
          相关资源
          最近更新 更多