Как сделать параллельное программирование в Python
Для C++ мы можем использовать OpenMP для параллельного программирования; однако OpenMP не будет работать для Python. Что я должен делать, если я хочу распараллелить некоторые части моей программы на Python?
Структура кода может рассматриваться как:
solve1(A)
solve2(B)
Где solve1
и solve2
две независимые функции. Как запустить такой код параллельно, а не последовательно, чтобы сократить время выполнения? Надеюсь, кто-нибудь может мне помочь. Большое спасибо заранее. Код является:
def solve(Q, G, n):
i = 0
tol = 10 ** -4
while i < 1000:
inneropt, partition, x = setinner(Q, G, n)
outeropt = setouter(Q, G, n)
if (outeropt - inneropt) / (1 + abs(outeropt) + abs(inneropt)) < tol:
break
node1 = partition[0]
node2 = partition[1]
G = updateGraph(G, node1, node2)
if i == 999:
print "Maximum iteration reaches"
print inneropt
Где setinner и setouter - две независимые функции. Вот где я хочу провести параллель...
Ответы
Ответ 1
Вы можете использовать модуль multiprocessing. Для этого случая я мог бы использовать пул обработки:
from multiprocessing import Pool
pool = Pool()
result1 = pool.apply_async(solve1, [A]) # evaluate "solve1(A)" asynchronously
result2 = pool.apply_async(solve2, [B]) # evaluate "solve2(B)" asynchronously
answer1 = result1.get(timeout=10)
answer2 = result2.get(timeout=10)
Это вызовет процессы, которые могут выполнять общую работу для вас. Поскольку мы не проходили processes
, это порождает один процесс для каждого ядра процессора на вашем компьютере. Каждое ядро ЦП может выполнять один процесс одновременно.
Если вы хотите сопоставить список с одной функцией, вы сделаете следующее:
args = [A, B]
results = pool.map(solve1, args)
Не используйте потоки, потому что GIL блокирует любые операции над объектами python.
Ответ 2
С Рэем это можно сделать очень элегантно.
Чтобы распараллелить ваш пример, вам нужно определить свои функции с @ray.remote
декоратора @ray.remote
, а затем вызвать их с помощью .remote
.
import ray
ray.init()
# Define the functions.
@ray.remote
def solve1(a):
return 1
@ray.remote
def solve2(b):
return 2
# Start two tasks in the background.
x_id = solve1.remote(0)
y_id = solve2.remote(1)
# Block until the tasks are done and get the results.
x, y = ray.get([x_id, y_id])
Это имеет ряд преимуществ перед многопроцессорным модулем.
- Тот же код будет работать на многоядерном компьютере, а также на кластере компьютеров.
- Процессы эффективно обмениваются данными через разделяемую память и сериализацию без копирования.
- Сообщения об ошибках распространяются хорошо.
-
Эти вызовы функций могут быть составлены вместе, например,
@ray.remote
def f(x):
return x + 1
x_id = f.remote(1)
y_id = f.remote(x_id)
z_id = f.remote(y_id)
ray.get(z_id) # returns 4
- В дополнение к удаленному вызову функций классы могут быть созданы удаленно как акторы.
Обратите внимание, что Ray - это фреймворк, который я помогал разрабатывать.
Ответ 3
Если вы имеете в виду параллельное программирование, вы можете использовать multiprocessing в Python.
Если вы хотите сделать какую-то хардкорную крупномасштабную параллельную аналитику данных, попробуйте Anaconda, которая теперь бесплатна.
Ответ 4
CPython использует Global Interpreter Lock, который делает параллельное программирование более интересным, чем С++
В этом разделе есть несколько полезных примеров и описаний проблемы:
Обходной путь Python Global Interpreter Lock (GIL) для многоядерных систем с использованием набора задач в Linux?
Ответ 5
Решение, как сказали другие, состоит в том, чтобы использовать несколько процессов. Однако то, какая структура является более подходящей, зависит от многих факторов. В дополнение к уже упомянутым, есть также charm4py и mpi4py (я разработчик charm4py).
Существует более эффективный способ реализации приведенного выше примера, чем использование абстракции рабочего пула. Основной цикл отправляет одни и те же параметры (включая полный граф G
) рабочим снова и снова на каждой из 1000 итераций. Поскольку по крайней мере один работник будет находиться в другом процессе, это включает копирование и отправку аргументов в другой процесс (ы). Это может быть очень дорого в зависимости от размера объектов. Вместо этого имеет смысл, чтобы работники хранили состояние и просто отправляли обновленную информацию.
Например, в charm4py это можно сделать так:
class Worker(Chare):
def __init__(self, Q, G, n):
self.G = G
...
def setinner(self, node1, node2):
self.updateGraph(node1, node2)
...
def solve(Q, G, n):
# create 2 workers, each on a different process, passing the initial state
worker_a = Chare(Worker, onPE=0, args=[Q, G, n])
worker_b = Chare(Worker, onPE=1, args=[Q, G, n])
while i < 1000:
result_a = worker_a.setinner(node1, node2, ret=True) # execute setinner on worker A
result_b = worker_b.setouter(node1, node2, ret=True) # execute setouter on worker B
inneropt, partition, x = result_a.get() # wait for result from worker A
outeropt = result_b.get() # wait for result from worker B
...
Обратите внимание, что для этого примера нам действительно нужен только один работник. Основной цикл может выполнять одну из функций, а рабочий может выполнять другую. Но мой код помогает проиллюстрировать несколько вещей:
- Рабочий A работает в процессе 0 (так же, как основной цикл). В то время как
result_a.get()
блокируется в ожидании результата, работник A выполняет вычисления в том же процессе. - Аргументы автоматически передаются по ссылке на работника A, поскольку он находится в том же процессе (копирование не выполняется).
Ответ 6
В некоторых случаях возможно автоматическое распараллеливание циклов с использованием Numba, хотя это работает только с небольшим подмножеством Python:
from numba import njit, prange
@njit(parallel=True)
def prange_test(A):
s = 0
# Without "parallel=True" in the jit-decorator
# the prange statement is equivalent to range
for i in prange(A.shape[0]):
s += A[i]
return s
К сожалению, кажется, что Numba работает только с массивами Numpy, но не с другими объектами Python. Теоретически также возможно скомпилировать Python в C++, а затем автоматически распараллелить его с помощью компилятора Intel C++, хотя я этого еще не пробовал.