Как сделать параллельное программирование в 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])

Это имеет ряд преимуществ перед многопроцессорным модулем.

  1. Тот же код будет работать на многоядерном компьютере, а также на кластере компьютеров.
  2. Процессы эффективно обмениваются данными через разделяемую память и сериализацию без копирования.
  3. Сообщения об ошибках распространяются хорошо.
  4. Эти вызовы функций могут быть составлены вместе, например,

    @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
    
  5. В дополнение к удаленному вызову функций классы могут быть созданы удаленно как акторы.

Обратите внимание, что Ray - это фреймворк, который я помогал разрабатывать.

Ответ 3

Если вы имеете в виду параллельное программирование, вы можете использовать multiprocessing в Python.

Если вы хотите сделать какую-то хардкорную крупномасштабную параллельную аналитику данных, попробуйте Anaconda, которая теперь бесплатна.

Ответ 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
        ...

Обратите внимание, что для этого примера нам действительно нужен только один работник. Основной цикл может выполнять одну из функций, а рабочий может выполнять другую. Но мой код помогает проиллюстрировать несколько вещей:

  1. Рабочий A работает в процессе 0 (так же, как основной цикл). В то время как result_a.get() блокируется в ожидании результата, работник A выполняет вычисления в том же процессе.
  2. Аргументы автоматически передаются по ссылке на работника 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++, хотя я этого еще не пробовал.