От вашего комментария: у вас относительно небольшое количество довольно больших элементов. Это означает, что скорость итерации внешнего цикла не имеет значения, в то время как скорость итераций внутренних циклов имеет решающее значение.
Приведем некоторые фактические цифры. Ваш внешний массив имеет до 4-х размеров до размера 10. Это означает, что существует до 10000 элементов. Между тем, элементы «довольно большой» -лет интерпретируют так консервативно, как всего 50. Итак, у вас есть итерации 510000. Все, что вы делаете, чтобы улучшить скорость 10000 внешних итераций, составит менее 2% разницы в вашем коде. Фактически, это намного меньше, чем то, что 2% полагают, что нет никакой работы, кроме самой итерации, что, очевидно, неверно.
Итак, вы фокусируетесь на неправильном месте. Только 500 000 внутренних итераций имеют значение. Если бы вы могли заменить массив массивов на один массив с одним размером и сделать все это в numpy
, это может быть быстрее, но сделать ваш код намного сложнее и трудно понять для выигрыша порядка нескольких 1% глупо. Просто используйте ответ vectorize
ответ или понимание, которые просты и очевидны.
Тем:
Вероятно, я должен попытаться распараллелить оценку, используя поток для каждого элемента матрицы.
Параллелизм - хорошая идея здесь. Но не использовать потоки, а не по одному элементу.
Если у вас есть, скажем, 8-ядерная машина, использующая 8 аппаратных потоков, вы получаете почти 8-кратную скорость. Но использование 10000 аппаратных потоков не означает, что вы делаете это в 10000 раз быстрее, или даже в 8 раз быстрее - вы, вероятно, тратите столько времени на переключение контекста, распределение и освобождение стеков потоков и т. Д., Что на самом деле это занимает больше времени, чем последовательный версия. Таким образом, вы хотите создать рабочий пул из 8 аппаратных потоков и вставить свои 10000 задач в очередь, которую должен запустить этот пул.
Кроме того, 10000, вероятно, слишком гранулирован. У каждой работы немного накладных расходов, и если ваши задания слишком малы, вы тратите слишком много времени на накладные расходы. Очевидные способы пакетной обработки на одну ось - 1000 заданий, каждый из которых выполняет одну строку из 10 элементов или 100 заданий, каждый из которых выполняет один 2D-массив из 100 элементов. Тестирование размеров партии 0, 1, 2 и 3 осей и см., Что дает лучшую производительность. Если различия огромны, вам может потребоваться несколько более сложное дозирование, например, разбиение на 3D-массивы 3x10x10 и 4x10x10.
Наконец, хотя потоки Python являются настоящими потоками операционной системы (и, следовательно, аппаратного), GIL предотвращает запуск одного из нескольких кодов Python за один раз. numpy
делает некоторые работы, чтобы обойти это, но это не идеально (особенно если у вас много накладных расходов между вызовами numpy
). Самый простой способ - использовать multiprocessing
, который позволяет иметь пул из 8 отдельных процессов вместо 8 потоков в 1 процессе. Это значительно увеличивает значительную нагрузку - вам либо нужно скопировать субмассивы (либо неявно, как параметры, так и возвращаемые значения функций задачи или явно через канал) или поместить их в общую память. Но если размер ваших задач достаточно велик, это обычно не проблема.
Вот пример того, как распараллелить существующий код:
f = np.mean
result = np.zeros(a.shape)
future_map = {}
with futures.ProcessPoolExecutor(max_workers=8) as executor:
for i in np.ndindex(a.shape):
future_map[executor.submit(f, a[i])] = i
for future in futures.as_completed(future_map):
i = future_map[future]
result[i] = future.result()
Очевидно, что вы могли бы упростить (например, заменить цикл представления с dict
понимания), но я хотел бы сделать это насколько это возможно, что вам нужно изменить.
Кроме того, я использую futures
(для чего требуется 3.2+, если вы используете 2,7, установите backport из PyPI), потому что он делает код немного проще; если вам нужна большая гибкость, вам нужна более сложная библиотека multiprocessing
.
И, наконец, я не делаю никаких дозированных операций - каждая задача - это один элемент, поэтому накладные расходы, вероятно, будут довольно плохими.
Но начните с этого, упростите его, насколько вам удобно, затем преобразуйте его, чтобы использовать партии 1, 2 и 3 осей и т. Д., Как описано ранее.
Что именно вы подразумеваете под «быстрым кодом»? Если 'a' всегда будет 2x2, но его элементы могут быть огромными, выполнение медленного внешнего цикла 2x2 не имеет никакого значения, если вы все еще выполняете быструю итерацию по внутренним петлям (что' np.mean' делает). – abarnert
быстрый способ «быстро по мере возможности». 'a' является матрицей' N x M x ... 'с N, M, ...~ 10, размеры должны быть <~ 4. Каждый элемент может быть довольно большим. Вероятно, я должен попытаться распараллелить оценку, используя поток для каждого матричного элемента. –
Но вы не делаете что-то как можно быстрее, оптимизируя что-то, что занимает менее 2% от общего времени. – abarnert