Какой объем выполнения кода я должен распараллелить? - PullRequest
5 голосов
/ 06 февраля 2010

Если я хочу распараллелить выполнение алгоритма, каковы маленькие куски кода, которые я должен разделить?

Классическим примером является алгоритм сортировки. Для какого размера элемента или типичного времени выполнения имеет смысл разделить сортировку между несколькими потоками? Или когда накладные расходы на ожидание в другом потоке превышают время выполнения в одном потоке?

Есть ли простые правила? Это зависит от ОС?

Ответы [ 4 ]

4 голосов
/ 07 февраля 2010

Ключевое правило: «разветвление только тогда, когда накладные расходы разветвления намного меньше, чем объем работы, который будет выполнять разветвление». Поскольку раздвоение накладных расходов является свойством конкретной технологии, которую вы используете, как и усилия по выполнению работы, вам, в некотором смысле, нужно определить это эмпирически. Скорее всего, в вашем коде появится некоторая пороговая константа настройки, представляющая этот компромисс.

Что вы обнаружите на практике, так это то, что найти отдельные части работы на самом деле сложно. Если вы сделаете рабочий блок небольшим, у него не будет много зависимостей, и вы можете запланировать его, как только все его входные потоки данных будут готовы. Но маленькие куски обычно означают небольшую работу, а накладные расходы обычно сводят на нет выигрыш. Если вы попытаетесь сделать чанки большими, у них будет так много зависимостей, что вы не сможете выделить их, чтобы запланировать их.

Некоторым людям повезло, и они могут найти такие большие куски; мы называем большинство из этих людей физиками и / или программистами на Фортране, и они используют преимущества параллелизма данных, вызванного делением мира на как можно большее количество крошечных кусочков.

Единственное приличное лекарство, которое я знаю, - это использование невероятно быстрого механизма разветвления, чтобы вы могли найти самые маленькие практичные куски. К сожалению, библиотеки параллелизма, предлагаемые для этого, являются ... библиотеками, вызываемыми динамически, с соответствующими накладными расходами динамического вызова. Типичным библиотекам, содержащим примитивы параллелизма, требуется от 100 до тысяч циклов для реализации «вилки»; Это плохая новость, если ваша работа составляет 100 машинных инструкций.

Я твердо верю, что для получения таких быстрых механизмов разветвления языковой компилятор должен знать, что вы делаете форк, например, «форк» (как бы там ни было написано :-) должен быть ключевым словом в языке. Затем компилятор может видеть вилки и предварительно выделять все необходимое для минимизации времени, необходимого для этого, и генерировать специальный код для управления шагами разветвления (и присоединения).

Язык PARLANSE , который я разработал и который мы используем в Semantic Designs, является одним из таких языков. Это Лисп-подобный язык в синтаксисе (но не в семантике). Его оператор параллелизма пишется "(|| ...)". Вы можете увидеть это ниже в модуле быстрой сортировки, который мы используем ежедневно, ниже. Вы также можете увидеть явное значение QuickSortParallelThreshold, определенное эмпирически. Эта быстрая сортировка линейно масштабируется до 8 ядер в системе Intel x86.

(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 голосов
/ 07 февраля 2010

Это зависит от издержек межпотоковой связи. Я тестировал openMP с обработкой изображений, и там была удобна линейка пикселей, которая давала хорошие ускорения. Мой образ был мегапиксельным, поэтому было 1000 заданий, что, вероятно, более чем достаточно, чтобы занять многоядерные машины сегодня. Вам также не нужно ограничиваться работой, которая занимает больше секунды или около того. В этом примере, где отчетливо видны ускорения заданий порядка 10 миллисекунд.

Теперь это был приятный алгоритм, потому что он не был рекурсивным, поэтому не было никаких зависимостей одной задачи от другой, и все задачи автоматически имели одинаковый размер.

Алгоритмы сортировки будут сложнее из-за разных размеров задач. Вы хотели бы иметь возможность экспериментировать с этим, и, возможно, выбрать вид, который легче паралеллизировать.

1 голос
/ 07 февраля 2010

Решение этой проблемы программно является одним из святых Граалей параллельных вычислений, и есть много библиотек, которые могут аппроксимировать оптимальный параллелизм для конкретных задач (например, Data Parallel Haskell).

Во всяком случае, чтобы сделать это вручную, нужно понимать:

  • Алгоритм, который вы хотите распараллелить (это распараллеливается?)
  • Характеристики данных, например размеры, местоположение (на диске, в памяти) и т. Д.
  • Аппаратное обеспечение, на котором вы работаете, например, число ядер, задержка памяти, размеры / строки / ассоциативность кэша и т. Д.
  • Модель потоков как языка реализации (сопрограммы, зеленые потоки, потоки ОС), так и ОС.
  • Стоимость порождения и переключения контекста между потоками.

Предполагая, что алгоритм распараллеливаем, ваша цель состоит в том, чтобы найти количество потоков и относительный размер порции данных, чтобы вы могли оптимально использовать оборудование для генерации решения.

Это довольно сложно сделать без большого количества экспериментов. Мой предпочтительный способ выяснить это - запустить множество тестов и получить данные о производительности как функцию одной или нескольких комбинаций из следующих:

  • Количество нитей.
  • Размеры буфера (если данные не находятся в ОЗУ) увеличиваются с некоторым разумным значением (например, размер блока, размер пакета, размер кэша и т. Д.)
  • Изменяющиеся размеры чанка (если вы можете обрабатывать данные постепенно).
  • Различные ручки настройки для ОС или языка исполнения.
  • Закрепление потоков на процессорах для улучшения локальности.
  • 1032 * Etc. *

В любом случае, это нелегкая задача, и есть инструменты и библиотеки, которые помогут вам выжать как можно большую производительность из ваших распараллеливаемых проблем. Единственный разумный способ сделать это правильно, хорошо понимая свои данные, код и среду выполнения.

1 голос
/ 07 февраля 2010

Пройдите пару курсов по параллельному и параллельному программированию. Познакомьтесь с несколькими технологиями, такими как обычная старая разветвленность или "ручная" многопоточность (потоки Java или pthreads), MPI, OpenMP, BSP, возможно даже CUDA или OpenCL. Затем либо решите стать экспертом, либо предоставьте экспертам возможность разрабатывать и внедрять эффективные и правильные параллельные алгоритмы. «Параллельная» часть - это легко, а «эффективная» и «правильная» - нет, когда нужны обе. Даже Java-коллекция Vector, разработанная и реализованная экспертами, не была свободна от ошибок в первых версиях. Простое определение модели памяти было неясно в первых версиях стандарта Java!

Самое простое правило: используйте готовые к использованию компоненты , разработанные и реализованные экспертами и не пытайтесь достичь как правильности, так и эффективности при разработке собственных параллельных алгоритмов, если только вы ре эксперт.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...