Пафос: варианты параллельной обработки - может кто-нибудь объяснить разницу?
Я пытаюсь запустить параллельные процессы под python (на Ubuntu).
Я начал использовать многопроцессорность, и она работала хорошо для простых примеров.
Затем произошла ошибка с засолкой, и я перешел на пафос. Я немного запутался с различными опциями и поэтому написал очень простой код для тестирования.
import multiprocessing as mp
from pathos.multiprocessing import Pool as Pool1
from pathos.pools import ParallelPool as Pool2
from pathos.parallel import ParallelPool as Pool3
import time
def square(x):
# calculate the square of the value of x
return x*x
if __name__ == '__main__':
dataset = range(0,10000)
start_time = time.time()
for d in dataset:
square(d)
print('test with no cores: %s seconds' %(time.time() - start_time))
nCores = 3
print('number of cores used: %s' %(nCores))
start_time = time.time()
p = mp.Pool(nCores)
p.map(square, dataset)
# Close
p.close()
p.join()
print('test with multiprocessing: %s seconds' %(time.time() - start_time))
start_time = time.time()
p = Pool1(nCores)
p.map(square, dataset)
# Close
p.close()
p.join()
print('test with pathos multiprocessing: %s seconds' %(time.time() - start_time))
start_time = time.time()
p = Pool2(nCores)
p.map(square, dataset)
# Close
p.close()
p.join()
print('test with pathos pools: %s seconds' %(time.time() - start_time))
start_time = time.time()
p = Pool3()
p.ncpus = nCores
p.map(square, dataset)
# Close
p.close()
p.join()
print('test with pathos parallel: %s seconds' %(time.time() - start_time))
Я понимаю
- 0,001 с простым последовательным кодом, без параллели,
- 0,100 с multiprocessing
вариант,
- 0,100 с pathos.multiprocessing
,
- 4.470 с pathos.pools
,
- ан AssertionError
ошибка с pathos.parallel
Я скопировал, как использовать эти различные варианты, с http://trac.mystic.cacr.caltech.edu/project/pathos/browser/pathos/examples.html
Я понимаю, что параллельная обработка для такого простого примера длиннее обычного последовательного кода. То, что я не понимаю, это относительная производительность пафоса.
Я проверил обсуждения, но не мог понять, почему pathos.pools
это намного дольше, и почему я получаю ошибку (не уверен тогда, какова будет производительность этого последнего варианта).
Я также попробовал с простой квадратной функцией, и для этого даже pathos.multiprocessing
намного дольше, чем multiprocessing
Может ли кто-нибудь объяснить разницу между этими вариантами?
Кроме того, я побежал pathos.multiprocessing
опция на удаленном компьютере, работает CentOS, и производительность примерно в 10 раз хуже, чем multiprocessing
,
Согласно компании, арендующей компьютер, он должен работать так же, как домашний компьютер. Я понимаю, что, возможно, будет трудно предоставить информацию без более подробной информации о машине, но если у вас есть какие-либо идеи относительно того, откуда она взялась, это помогло бы.
2 ответа
Я pathos
автор. Извините за путаницу. Вы имеете дело со смесью старого и нового интерфейса программирования.
"Новый" (предлагаемый) интерфейс должен использовать pathos.pools
, Старый интерфейс связан с одними и теми же объектами, так что на самом деле это два способа добраться до одной и той же вещи.
multiprocess.Pool
это вилка multiprocessing.Pool
с той лишь разницей, что multiprocessing
использования pickle
а также multiprocess
использования dill
, Таким образом, я ожидаю, что скорость будет одинаковой в большинстве простых случаев.
Вышеупомянутый бассейн также можно найти на pathos.pools._ProcessPool
, pathos
предоставляет небольшую оболочку для нескольких типов пулов с разными бэкэндами, предоставляя расширенную функциональность. pathos
обернутый бассейн pathos.pools.ProcessPool
(и старый интерфейс предоставляет его в pathos.multiprocessing.Pool
).
Предпочтительный интерфейс pathos.pools.ProcessPool
,
Там также ParallelPool
, который использует другой бэкэнд - он использует ppft
вместо multiprocess
, ppft
это "параллельный питон", который порождает процессы питона через subprocess
и передает исходный код (с dill.source
вместо сериализованных объектов) - он предназначен для распределенных вычислений, или когда лучше передать исходный код.
Так, pathos.pools.ParallelPool
является предпочтительным интерфейсом, и pathos.parallel.ParallelPool
(и несколько других подобных ссылок в pathos
) болтаются по наследству - но это один и тот же объект внизу.
В итоге:
>>> import multiprocessing as mp
>>> mp.Pool()
<multiprocessing.pool.Pool object at 0x10fa6b6d0>
>>> import multiprocess as mp
>>> mp.Pool()
<multiprocess.pool.Pool object at 0x11000c910>
>>> import pathos as pa
>>> pa.pools._ProcessPool()
<multiprocess.pool.Pool object at 0x11008b0d0>
>>> pa.multiprocessing.Pool()
<multiprocess.pool.Pool object at 0x11008bb10>
>>> pa.pools.ProcessPool()
<pool ProcessPool(ncpus=4)>
>>> pa.pools.ParallelPool()
<pool ParallelPool(ncpus=*, servers=None)>
Вы можете увидеть ParallelPool
имеет servers
... таким образом, предназначен для распределенных вычислений.
Единственный оставшийся вопрос - почему AssertionError
? Ну, это потому, что обертка, которая pathos
add сохраняет объект пула доступным для повторного использования. Следовательно, когда вы звоните ParallelPool
во второй раз вы звоните в закрытый пул. Вам нужно restart
пул, чтобы включить его снова.
>>> f = lambda x:x
>>> p = pa.pools.ParallelPool()
>>> p.map(f, [1,2,3])
[1, 2, 3]
>>> p.close()
>>> p.join()
>>> p.restart() # throws AssertionError w/o this
>>> p.map(f, [1,2,3])
[1, 2, 3]
>>> p.close()
>>> p.join()
>>> p.clear() # destroy the saved pool
ProcessPool
имеет тот же интерфейс, что и ParallelPool
относительно перезапуска и очистки сохраненных экземпляров.
Может ли кто-нибудь объяснить различия?
Давайте начнем с некоторой точки соприкосновения.
Интерпретатор Python, как правило, использует пошаговое выполнение кода GIL. Это означает, что все основанные на потоках пулы все еще ожидают упорядочения по шагам GIL всех путей выполнения кода, поэтому любая такая созданная попытка не будет иметь преимуществ, " теоретически ожидаемых ".
Интерпретатор Python может использовать другие экземпляры, основанные на процессах, для загрузки нескольких процессов, каждый из которых имеет свою собственную GIL-блокировку, формируя пул из нескольких параллельных путей выполнения кода.
Управляя этим основным неоднозначностью, вопросы, связанные с производительностью, начинают появляться далее. Наиболее ответственный подход - это эталон, эталон, эталон. Здесь нет исключений.
Что нужно так много времени, чтобы провести здесь (где)?
Основная (постоянная) часть - это прежде всего [TIME]
-основная стоимость процесса-реализации. Здесь полная копия интерпретатора Python, включая все переменные, все карты памяти, действительно полная полная копия состояния вызывающего интерпретатора Python, должна быть сначала создана и помещена в таблицу планировщика процессов операционной системы, прежде чем выполнять какие-либо дальнейшие действия. (полезная часть работы) вычисление "внутри" такого успешно созданного экземпляра подпроцесса. Если ваша функция полезной нагрузки сразу же возвращается оттуда, создав x*x
ваш код, похоже, сжег все это топливо за несколько инструкций процессора, и вы потратили намного больше, чем получили взамен. Экономия затрат идет против вас, так как все затраты на завершение процесса и завершение процесса намного выше, чем несколько тактов CPU-CLOCK.
Как долго это на самом деле занимает?
Вы можете сравнить это (как предложено здесь, в предложенном Test-Case-A
, Если Stopwatch()
-ed [us]
решите, что вы начнете полагаться на факты больше, чем на любые советы подражателей или советы по маркетингу. Это справедливо, не правда ли?).
Test-Case-A
эталонные затраты на реализацию процесса [ИЗМЕРЕНО].
Что дальше?
Следующая наиболее опасная (переменная по размеру) часть - это прежде всего [SPACE]
-домен расходы, но имея также [TIME]
-доменное воздействие, если [SPACE]
-распределение затрат начинает расти за рамками небольших размеров.
Подобные накладные расходы такого рода связаны с любой необходимостью передавать параметры "большого" размера от интерпретатора "main"-Python до каждого из (распределенных) экземпляров подпроцесса.
Как долго это займет?
Опять же, бенчмарк, бенчмарк, бенчмарк. Отметим это (как предложено здесь, если расширение там предложено Test-Case-C
с заменой aNeverConsumedPAR
параметр с некоторым действительно "толстым" блоком данных, будь то numpy.ndarray()
или другой тип, несущий огромный след памяти.)
Таким образом, реальные затраты на аппаратные средства, связанные с O/S, связанные с питоном, начинают становиться видимыми и измеряться в таком эталоне, как дополнительные накладные расходы в **[us]**
, Это не является чем-то новым для хакеров, но люди, которые никогда не сталкивались с тем, что время записи на жесткий диск может вырасти и блокировать другую обработку на многие секунды или минуты, вряд ли поверили бы, если бы не касались собственного сравнительного анализа реальной стоимости поток данных. Итак, не стесняйтесь расширять тест Test-Case-C
действительно большие следы памяти, чтобы чувствовать запах дыма...
И последнее, но не менее важное: переформулированный Закон Амдала скажет...
если попытаться распараллелить некоторые вычисления, то они хорошо понятны как для вычислительной части, так и для всех служебных частей, картина начинает завершаться:
Переформулировка Закона Амдаля о строгих и ресурсоемких законах показывает:
1
S = ______________________________________________ ; where s,
/ \ ( 1 - s ),
| ( 1 - s ) | pSO,
s + pSO + max| _________ , atomicP | + pTO pTO,
| N | N
\ / have been defined in
just an Overhead-strict Law
and
atomicP := is a further indivisible duration of an atomic-process-block
Это в результате ускорения S
всегда будет страдать от высоких накладных расходов pSO + pTO
так же, как когда бы то ни было высокий N
не будет допущено к дальнейшей помощи, из-за достаточно высокой стоимости atomicP
,
Во всех этих случаях окончательное ускорение S
может легко попасть под << 1.0
да, хорошо под чистым [SERIAL]
график выполнения кода (опять же, сравнив реальные затраты на pSO
а также pTO
(для которого схематически был предложен Test-Case-A + Test-Case-C (расширенный)) появляется возможность получить минимальную разумную вычислительную нагрузку, необходимую для того, чтобы оставаться выше мистического уровня ускорения >= 1.0