У меня есть этот код, он генерирует 2 000 000 точек, равномерно распределенных в ограничивающем прямоугольнике, и выполняет некоторые вычисления для разделения точек на основе некоторых критериев.
import numpy as np
from draw import draw
import time
X = 0
Y = 1
N = 2000000
max_x = -100000
max_y = -100000
min_x = 100000
min_y = 100000
points = np.random.uniform(-10, 10, (N, 2))
start = time.time()
max_x_index = np.argmax(points[:, X])
max_y_index = np.argmax(points[:, Y])
min_x_index = np.argmin(points[:, X])
min_y_index = np.argmin(points[:, Y])
p_right = points[max_x_index]
p_top = points[max_y_index]
p_left = points[min_x_index]
p_bottom = points[min_y_index]
top_right = points[
points[:, X] > ((points[:, Y] - p_top[Y]) / (p_right[Y] - p_top[Y])) * (p_right[X] - p_top[X]) + p_top[X]]
top_left = points[
points[:, X] < ((points[:, Y] - p_top[Y]) / (p_left[Y] - p_top[Y])) * (p_left[X] - p_top[X]) + p_top[X]]
bottom_right = points[
points[:, X] > ((points[:, Y] - p_bottom[Y]) / (p_right[Y] - p_bottom[Y])) * (p_right[X] - p_bottom[X]) + p_bottom[
X]]
bottom_left = points[
points[:, X] < ((points[:, Y] - p_bottom[Y]) / (p_left[Y] - p_bottom[Y])) * (p_left[X] - p_bottom[X]) + p_bottom[X]]
end = time.time()
print(end - start)
Выход обычно равен 0,09, что соответствует секундам. Самый трудоемкий раздел — это последние четыре вычисления для получения значений top_right, top_left, Bottom_right и Bottom_left. Я переписываю код следующим образом
import numpy as np
from draw import draw
import time
import multiprocessing
N = 2000000
X = 0
Y = 1
points = np.random.uniform(-10, 10, (N, 2))
max_x = -100000
max_y = -100000
min_x = 100000
min_y = 100000
manager = multiprocessing.Manager()
top_right = manager.list()
top_left = manager.list()
bottom_right = manager.list()
bottom_left = manager.list()
def set_top_right():
global X, Y, points, p_top, p_right, top_right
top_right.extend(points[
points[:, X] > ((points[:, Y] - p_top[Y]) / (p_right[Y] - p_top[Y])) * (p_right[X] - p_top[X]) + p_top[X]])
def set_top_left():
global X, Y, points, p_top, p_left, top_left
top_left.extend(points[
points[:, X] < ((points[:, Y] - p_top[Y]) / (p_left[Y] - p_top[Y])) * (p_left[X] - p_top[X]) + p_top[X]])
def set_bottom_right():
global X, Y, points, p_bottom, p_right, bottom_right
bottom_right.extend(points[
points[:, X] > ((points[:, Y] - p_bottom[Y]) / (p_right[Y] - p_bottom[Y])) * (p_right[X] - p_bottom[X]) +
p_bottom[X]])
def set_bottom_left():
global X, Y, points, p_bottom, p_left, bottom_left
bottom_left.extend(points[
points[:, X] < ((points[:, Y] - p_bottom[Y]) / (p_left[Y] - p_bottom[Y])) * (p_left[X] - p_bottom[X]) +
p_bottom[X]])
start = time.time()
max_x_index = np.argmax(points[:, X])
max_y_index = np.argmax(points[:, Y])
min_x_index = np.argmin(points[:, X])
min_y_index = np.argmin(points[:, Y])
p_right = points[max_x_index]
p_top = points[max_y_index]
p_left = points[min_x_index]
p_bottom = points[min_y_index]
p1 = multiprocessing.Process(target=set_top_right)
p2 = multiprocessing.Process(target=set_top_left)
p3 = multiprocessing.Process(target=set_bottom_right)
p4 = multiprocessing.Process(target=set_bottom_left)
p1.start()
p2.start()
p3.start()
p4.start()
p1.join()
p2.join()
p3.join()
p4.join()
end = time.time()
print(end - start)
и на удивление стало хуже, примерно на 0,15 секунды. Я почти новичок в Python, однако думаю, что оба подхода представляют собой один поток и нет ввода-вывода. операции. Процессор моего ноутбука core i5
11-го поколения, и я ожидал, что каждое ядро возьмет на себя один из процессов и сделает его быстрее. Тогда почему это медленнее?
@jabaa Да. p1.start() p2.start() p3.start() p4.start()
занимает 0,14 секунды.
Возможно, это накладные расходы на многопроцессорную обработку. Есть ли у вас данные профилирования для set_top_right
, set_top_left
, set_bottom_right
и set_bottom_left
? Если это ~ 0,01–0,02 секунды, вероятно, это накладные расходы.
Вы сравниваете общую память list
с пустым ndarray
. Это требует огромных затрат на сериализацию. Честно говоря, спасибо реализации общей памяти за то, что она лишь немного медленнее.
Что мне тогда делать? Как я уже сказал, я новичок в Python
Почему вы хотите использовать многопроцессорность для приложения, которое работает 0,09 секунды?
Это первый шаг алгоритма предварительной обработки. Математически алгоритм работает за O(n), мне нужна лучшая реализация. Поэтому я стараюсь сделать каждый шаг как можно лучше.
кроме того, все ваши утверждения global
бессмысленны и ничего не делают (по моему мнению, они должны вызывать ошибку)
работа с numpy
часто на порядки быстрее, чем работа с list
, особенно с manager.List
, который требует сериализации и десериализации. Обратите внимание: вы по-прежнему копируете points
и все остальные состояния в каждом подпроцессе. Использование manager.List
вряд ли победит серийный номер.
@juanpa.arrivillaga Могу ли я разделить массив между процессами? Я думаю, что это решит проблему
@MamaD да, потенциально, особенно с numpy, это возможно. Однако я сверился с библиотекой под названием numexpr
, на которую вам следует обратить внимание. Смотри мой
numpy уже выполняет распараллеливание, и все это происходит внутри одного процесса (избегая затрат на перемещение данных через границы процесса, что, как правило, важно). Когда у вас есть задание, которое numpy может распараллелить, вы почти никогда не захотите использовать многопроцессорность, которая требует гораздо больше накладных расходов. (многопроцессорность — это не серебряный молоток, она ускоряет работу только в очень узких, специфических случаях). А совместное использование как numpy, так и многопроцессорной обработки означает, что вы получаете конкуренцию за процессор, замедляющую ваше работу, поскольку каждый из них пытается использовать все ваши ядра, а это означает, что в конечном итоге у вас будет больше требований, чем у вас есть процессоры.
@CharlesDuffy, это зависит. Но об этом определенно стоит помнить.
@juanpa.arrivillaga, честно, отредактировано, добавлено «почти». :)
(другими словами: многопроцессорность — это инструмент, специально предназначенный для решения проблемы глобальной блокировки интерпретатора, предотвращающей одновременное изменение состояния интерпретатора Python несколькими потоками, но часть смысла numpy заключается в том, что он реализован на C и не подлежит конкуренции за GIL между различными потоками, которые сам numpy порождает как часть одной операции, поэтому, когда вы используете numpy корректным и подходящим способом, вы, как правило, не сталкиваетесь с узким местом из-за GIL, поэтому вы; оплатить затраты на многопроцессорную обработку, но не получить никакой выгоды).
@CharlesDuffy Все это очень верно, и, конечно, первый шаг — узнать, можете ли вы поэкспериментировать со своим numpy-дистрибутивом (например, использовать Intel VML), хотя вы можете использовать numpy
и multiprodessing.shared_memory.SharedMemory
, чтобы использовать numpy.ndarray
, который напрямую использует общую память. . Вот интересный вопрос/ответ, который касается различных способов работы numpy (а также рассказывает о numba
и numexpr
)
Многопроцессорная обработка с помощью multiprocessing.Manager.List
не будет быстрее, вы понесете накладные расходы IPC из-за копирования состояния. Теперь вы, возможно, сможете эффективно использовать multiprocessing.SharedMemory
с массивами numpy, однако я бы также предложил попробовать numexpr, который должен помочь в этом конкретном случае использования множества операций, создающих ненужные промежуточные массивы. Вот простой тест:
import numpy as np
import numexpr as ne
import time
X = 0
Y = 1
N = 2000000
max_x = -100000
max_y = -100000
min_x = 100000
min_y = 100000
points = np.random.uniform(-10, 10, (N, 2))
max_x_index = np.argmax(points[:, X])
max_y_index = np.argmax(points[:, Y])
min_x_index = np.argmin(points[:, X])
min_y_index = np.argmin(points[:, Y])
p_right = points[max_x_index]
p_top = points[max_y_index]
p_left = points[min_x_index]
p_bottom = points[min_y_index]
start = time.perf_counter()
def pure_numpy():
top_right = points[
points[:, X] > ((points[:, Y] - p_top[Y]) / (p_right[Y] - p_top[Y])) * (p_right[X] - p_top[X]) + p_top[X]
]
top_left = points[
points[:, X] < ((points[:, Y] - p_top[Y]) / (p_left[Y] - p_top[Y])) * (p_left[X] - p_top[X]) + p_top[X]
]
bottom_right = points[
points[:, X] > ((points[:, Y] - p_bottom[Y]) / (p_right[Y] - p_bottom[Y])) * (p_right[X] - p_bottom[X]) + p_bottom[X]
]
bottom_left = points[
points[:, X] < ((points[:, Y] - p_bottom[Y]) / (p_left[Y] - p_bottom[Y])) * (p_left[X] - p_bottom[X]) + p_bottom[X]
]
for _ in range(100):
pure_numpy()
print(time.perf_counter() - start)
start = time.perf_counter()
def using_numexpr():
top_right = points[
ne.evaluate(
# points[:, X] > ((points[:, Y] - p_top[Y]) / (p_right[Y] - p_top[Y])) * (p_right[X] - p_top[X]) + p_top[X]
"pointsX > ((pointsY - p_topY) / (p_rightY - p_topY)) * (p_rightX - p_topX) + p_topX",
dict(pointsX=points[:, X], pointsY=points[:, Y], p_topY=p_top[Y], p_rightY=p_right[Y], p_rightX=p_right[X], p_topX=p_top[X])
)
]
top_left = points[
# points[:, X] < ((points[:, Y] - p_top[Y]) / (p_left[Y] - p_top[Y])) * (p_left[X] - p_top[X]) + p_top[X]
ne.evaluate(
"pointsX < ((pointsY - p_topY) / (p_leftY - p_topY)) * (p_leftX - p_topX) + p_topX",
dict(pointsX=points[:, X], pointsY=points[:, Y], p_topY=p_top[Y], p_leftY=p_left[Y], p_leftX=p_left[X], p_topX=p_top[X])
)
]
bottom_right = points[
# points[:, X] > ((points[:, Y] - p_bottom[Y]) / (p_right[Y] - p_bottom[Y])) * (p_right[X] - p_bottom[X]) + p_bottom[X]
ne.evaluate(
"pointsX > ((pointsY - p_bottomY) / (p_rightY - p_bottomY)) * (p_rightX - p_bottomX) + p_bottomX",
dict(pointsX=points[:, X], pointsY=points[:, Y], p_bottomY=p_bottom[Y], p_rightY=p_right[Y], p_rightX=p_right[X], p_bottomX=p_bottom[X])
)
]
bottom_left = points[
#points[:, X] < ((points[:, Y] - p_bottom[Y]) / (p_left[Y] - p_bottom[Y])) * (p_left[X] - p_bottom[X]) + p_bottom[X]
ne.evaluate(
"pointsX < ((pointsY - p_bottomY) / (p_leftY - p_bottomY)) * (p_leftX - p_bottomX) + p_bottomX",
dict(pointsX=points[:, X], pointsY=points[:, Y], p_bottomY=p_bottom[Y], p_leftY=p_left[Y], p_leftX=p_left[X], p_bottomX=p_bottom[X])
)
]
for _ in range(100):
using_numexpr()
print(time.perf_counter() - start)
Я получаю такие результаты:
(py312) Juans-MBP:foo juan$ python testing.py
10.898050098097883
7.235401042038575
Так стабильно быстрее.
Я использую свой 10-летний ноутбук только с 4 логическими ядрами, numexpr
использует несколько ядер, он мог бы работать лучше на более качественной машине. Вы также можете изучить различные способы повышения производительности, например, используя библиотеку векторных математических вычислений Intel.
Если вы хотите использовать multiprocessing
, вы можете использовать multiprocessing.shared_memory
, но для восстановления объекта массива numpy из общей памяти требуется определенная осторожность (отслеживание типов и формы). См. этот ответ на другой вопрос, конкретно о том, как это сделать
Это заняло всего 0,03 секунды. Большой! Спасибо. Не могли бы вы порекомендовать хорошие ссылки на метод, который вы использовали? Это только первая часть алгоритма в области вычислительной геометрии в информатике, и есть еще много оставшихся частей, которые мне нужно реализовать с помощью многопроцессорной обработки, поэтому мне нужно изучить
@MamaD Я не использовал многопроцессорность, я использовал numexpr
специфичную для numpy библиотеку, я дал ссылку на руководство пользователя для numexpr
библиотеки выше, но вот это снова. Я только что перевел ваш код напрямую. Подробнее о библиотеке читайте по ссылке. Он в основном предназначен для этого конкретного случая использования.
Алгоритм должен рекурсивно повторить аналогичный процесс для точек в четырех регионах top_right
, top_left
, bottom_right
, bottom_left
. Рекомендуете ли вы мне использовать многопроцессорность для такого рекурсивного процесса?
@MamaD в этом вопросе рекомендуется использовать numexpr
, а не многопроцессорность. Мультиобработка — это вообще другой подход, который вам определенно не следует совмещать с numpexpr
, потому что numexpr
уже задействует все ядра. многопроцессорная обработка с общей памятью немного сложна, но осуществима. Хотя вам придется протестировать, чтобы сравнить его с этим numexpr
подходом.
Вы делали профилирование? Где узкое место?