Подтвердить что ты не робот

Python - многопроцессорная обработка для matplotlib griddata

Следуя моему предыдущему вопросу [1], я хотел бы применить многопроцессорность к функции matplotlib griddata. Можно ли разделить гриддаты на, скажем, 4 части, по одному для каждого из моих 4 ядер? Мне нужно это для повышения производительности.

Например, попробуйте код ниже, экспериментируя с разными значениями для size:

import numpy as np
import matplotlib.mlab as mlab
import time

size = 500

Y = np.arange(size)
X = np.arange(size)
x, y = np.meshgrid(X, Y)
u = x * np.sin(5) + y * np.cos(5)
v = x * np.cos(5) + y * np.sin(5)
test = x + y

tic = time.clock()

test_d = mlab.griddata(
    x.flatten(), y.flatten(), test.flatten(), x+u, y+v, interp='linear')

toc = time.clock()

print 'Time=', toc-tic
4b9b3361

Ответ 1

Я запустил пример кода ниже в Python 3.4.2 с numpy версии 1.9.1 и matplotlib версии 1.4.2 на Macbook Pro с 4 физическими процессорами (т.е. в отличие от "виртуальных" процессоров, которые Mac аппаратная архитектура также доступна для некоторых случаев использования):

import numpy as np
import matplotlib.mlab as mlab
import time
import multiprocessing

# This value should be set much larger than nprocs, defined later below
size = 500

Y = np.arange(size)
X = np.arange(size)
x, y = np.meshgrid(X, Y)
u = x * np.sin(5) + y * np.cos(5)
v = x * np.cos(5) + y * np.sin(5)
test = x + y

tic = time.clock()

test_d = mlab.griddata(
    x.flatten(), y.flatten(), test.flatten(), x+u, y+v, interp='linear')

toc = time.clock()

print('Single Processor Time={0}'.format(toc-tic))

# Put interpolation points into a single array so that we can slice it easily
xi = x + u
yi = y + v
# My example test machine has 4 physical CPUs
nprocs = 4
jump = int(size/nprocs)

# Enclose the griddata function in a wrapper which will communicate its
# output result back to the calling process via a Queue
def wrapper(x, y, z, xi, yi, q):
    test_w = mlab.griddata(x, y, z, xi, yi, interp='linear')
    q.put(test_w)

# Measure the elapsed time for multiprocessing separately
ticm = time.clock()

queue, process = [], []
for n in range(nprocs):
    queue.append(multiprocessing.Queue())
    # Handle the possibility that size is not evenly divisible by nprocs
    if n == (nprocs-1):
        finalidx = size
    else:
        finalidx = (n + 1) * jump
    # Define the arguments, dividing the interpolation variables into
    # nprocs roughly evenly sized slices
    argtuple = (x.flatten(), y.flatten(), test.flatten(),
                xi[:,(n*jump):finalidx], yi[:,(n*jump):finalidx], queue[-1])
    # Create the processes, and launch them
    process.append(multiprocessing.Process(target=wrapper, args=argtuple))
    process[-1].start()

# Initialize an array to hold the return value, and make sure that it is
# null-valued but of the appropriate size
test_m = np.asarray([[] for s in range(size)])
# Read the individual results back from the queues and concatenate them
# into the return array
for q, p in zip(queue, process):
    test_m = np.concatenate((test_m, q.get()), axis=1)
    p.join()

tocm = time.clock()

print('Multiprocessing Time={0}'.format(tocm-ticm))

# Check that the result of both methods is actually the same; should raise
# an AssertionError exception if assertion is not True
assert np.all(test_d == test_m)

и я получил следующий результат:

/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/site-packages/matplotlib/tri/triangulation.py:110: FutureWarning: comparison to `None` will result in an elementwise object comparison in the future.self._neighbors)
Single Processor Time=8.495998
Multiprocessing Time=2.249938

Я не совсем уверен, что вызывает "будущее предупреждение" от triangulation.py(очевидно, моя версия matplotlib не понравилась что-то о входных значениях, которые изначально были предоставлены для вопроса), но независимо от того, многопроцессорность , похоже, достигнет желаемого ускорения 8.50/2.25 = 3.8 (изменить: см. комментарии), что примерно примерно в 4X, что мы ожидаем для машины с 4 процессорами. И утверждение утверждения в конце также успешно выполняется, доказывая, что оба метода получают один и тот же ответ, поэтому, несмотря на слегка странное предупреждающее сообщение, я считаю, что приведенный выше код является допустимым решением.


EDIT: комментатор указал, что как мое решение, так и фрагмент кода, опубликованный оригинальным автором, скорее всего, используют неправильный метод time.clock() для измерения времени выполнения; он предлагает использовать time.time() вместо этого. Думаю, я тоже приду к его точке зрения. (Копаясь в документации Python немного дальше, я все еще не убежден, что даже это решение на 100% правильное, поскольку более новые версии Python, похоже, устарели time.clock() в пользу time.perf_counter() и time.process_time(). Но независимо от того, согласен ли я, что или нет time.time() - это абсолютно правильный способ измерения, это, вероятно, более корректно, чем то, что я использовал раньше, time.clock().)

Предполагая, что точка комментария верна, значит, примерно 4-кратное ускорение, которое, как я думал, я измерил, на самом деле ошибочно.

Однако это не означает, что основной код не был правильно распараллелен; скорее, это просто означает, что распараллеливание в действительности не помогло; разделение данных и работа на нескольких процессорах ничего не улучшали. Почему это должно быть? Другие пользователи указали , что, по крайней мере, в numpy/scipy, некоторые функции работают на нескольких ядрах, а некоторые нет, и это может быть серьезный исследовательский проект для конечный пользователь, чтобы попытаться выяснить, какие из них являются.

Основываясь на результатах этого эксперимента, если мое решение правильно достигает распараллеливания в Python, но дальнейшего ускорения не наблюдается, то я бы предположил, что наиболее вероятным объяснением является то, что matplotlib, вероятно, также распараллеливает некоторые его функции "под капотом" ", так сказать, в скомпилированных библиотеках С++, как и numpy/scipy. Предполагая, что случай, тогда правильный ответ на этот вопрос заключался бы в том, что дальше ничего не может быть сделано: дальнейшее распараллеливание в Python не принесет пользы, если базовые библиотеки С++ уже молча работают на нескольких ядрах для начала.