Я ищу окончательный ответ на партитуру MATLAB для Python (Scipy, Numpy).
Есть ли решение, подобное parfor? Если нет, то каково усложнение для его создания?
UPDATE: Вот типичный цифровой вычислительный код, который мне нужно ускорить
import numpy as np
N = 2000
output = np.zeros([N,N])
for i in range(N):
for j in range(N):
output[i,j] = HeavyComputationThatIsThreadSafe(i,j)
Примером тяжелой вычислительной функции является:
import scipy.optimize
def HeavyComputationThatIsThreadSafe(i,j):
n = i * j
return scipy.optimize.anneal(lambda x: np.sum((x-np.arange(n)**2)), np.random.random((n,1)))[0][0,0]
Существует множество сред Python для параллельных вычислений. Больше всего мне нравится IPython, но я не знаю много о других. В IPython одним аналогом parfor будет client.MultiEngineClient.map()
или некоторые другие конструкции в документации по быстрому и простому параллелизму.
Один встроенный в python будет multiprocessing
docs здесь. Я всегда использую multiprocessing.Pool
с таким количеством рабочих, как процессоры. Тогда всякий раз, когда мне нужно создать структуру, похожую на петлю, я использую Pool.imap
Пока тело вашей функции не зависит от предыдущей итерации, вы должны иметь линейное ускорение. Это также требует, чтобы ваши входы и выходы были pickle
-able, но это довольно легко обеспечить для стандартных типов.
UPDATE: Некоторый код для вашей обновленной функции просто показывает, насколько это просто:
from multiprocessing import Pool
from itertools import product
output = np.zeros((N,N))
pool = Pool() #defaults to number of available CPU's
chunksize = 20 #this may take some guessing ... take a look at the docs to decide
for ind, res in enumerate(pool.imap(Fun, product(xrange(N), xrange(N))), chunksize):
output.flat[ind] = res
output[ind]
на output.flat[ind]
чтобы код работал. ( output
представляет собой двумерный массив, для которого потребуются два индекса.)
Я всегда использовал Parallel Python, но это не полный аналог, поскольку я считаю, что он обычно использует отдельные процессы, которые могут быть дорогими при определенных операциях системы. Тем не менее, если тело ваших петель достаточно короткое, это не имеет значения и может иметь некоторые преимущества.
Чтобы увидеть пример, предположите, что вы хотите записать эквивалентность этого кода Matlab в Python
matlabpool open 4
parfor n=0:9
for i=1:10000
for j=1:10000
s=j*i
end
end
n
end
disp('done')
Как можно написать это в python, особенно в ноутбуке jupyter. Вы должны создать функцию в рабочем каталоге (я назвал ее FunForParFor.py), которая имеет следующие
def func(n):
for i in range(10000):
for j in range(10000):
s=j*i
print(n)
Затем я перехожу к своему ноутбуку Jupyter и пишу следующий код
import multiprocessing
import FunForParFor
if __name__ == '__main__':
pool = multiprocessing.Pool(processes=4)
pool.map(FunForParFor.func, range(10))
pool.close()
pool.join()
print('done')
Это сработало для меня! Я просто хотел поделиться им здесь, чтобы дать вам конкретный пример.
Это может быть сделано элегантно с Ray, системой, которая позволяет вам легко распараллеливать и распространять ваш код Python.
Чтобы распараллелить ваш пример, вам нужно определить свои функции с @ray.remote
декоратора @ray.remote
, а затем вызвать их с помощью .remote
.
import numpy as np
import time
import ray
ray.init()
# Define the function. Each remote function will be executed
# in a separate process.
@ray.remote
def HeavyComputationThatIsThreadSafe(i, j):
n = i*j
time.sleep(0.5) # Simulate some heavy computation.
return n
N = 10
output_ids = []
for i in range(N):
for j in range(N):
# Remote functions return a future, i.e, an identifier to the
# result, rather than the result itself. This allows invoking
# the next remote function before the previous finished, which
# leads to the remote functions being executed in parallel.
output_ids.append(HeavyComputationThatIsThreadSafe.remote(i,j))
# Get results when ready.
output_list = ray.get(output_ids)
# Move results into an NxN numpy array.
outputs = np.array(output_list).reshape(N, N)
# This program should take approximately N*N*0.5s/p, where
# p is the number of cores on your machine, N*N
# is the number of times we invoke the remote function,
# and 0.5s is the time it takes to execute one instance
# of the remote function. For example, for two cores this
# program will take approximately 25sec.
Существует несколько преимуществ использования Ray по сравнению с многопроцессорным модулем. В частности, один и тот же код будет работать как на одной машине, так и на кластере машин. Для получения дополнительных преимуществ Рэй см. Этот пост.
Примечание. Следует иметь в виду, что каждая удаленная функция выполняется в отдельном процессе, возможно, на другом компьютере, и, таким образом, вычисление удаленной функции должно занять больше, чем вызов удаленной функции. Как правило, вычисление удаленной функции должно занимать не менее нескольких 10 мсек, чтобы амортизировать затраты на планирование и запуск удаленной функции.
Я перепробовал все решения здесь, но обнаружил, что самый простой способ и самый близкий эквивалент matlabs parfor - это шутка Нумбы.
По сути, вы меняете одну букву в вашем цикле, диапазон варьируется:
from numba import autojit, prange
@autojit
def parallel_sum(A):
sum = 0.0
for i in prange(A.shape[0]):
sum += A[i]
return sum