#python #arrays #variables
Вопрос:
Я работал над оптимизацией некоторых вычислений преобразования евклидова расстояния для программы, которую я создаю. Для предисловия, у меня мало формального образования в области компьютерных наук, кроме некоторых курсов, которые я посещал.
Я узнал из эмпирического тестирования в Python, что присвоение значений отдельным переменным и выполнение операций с ними происходит быстрее, чем выполнение операций с массивами. Можно ли это наблюдение воспроизвести для других?
Если да, то не мог бы кто-нибудь более подробно объяснить, почему существуют такие различия в скорости между этими двумя формами синтаксиса?
Пожалуйста, посмотрите пример кода ниже.
import numpy as np
from math import sqrt
import time
# Numpy array math
def test1(coords):
results = []
for coord in coords:
mins = np.array([1,1,1])
# The three lines below seem faster than np.linalg.norm()
mins = (coord - mins)**2
mins = np.sum(mins)
results.append(sqrt(mins))
# Individual variable assignment math
def test2(coords):
results = []
for point in coords:
z, y, x = 1, 1, 1
z = (point[0] - z)**2
y = (point[1] - y)**2
x = (point[2] - x)**2
mins = sqrt(z y x)
results.append(mins)
a = np.random.randint(0, 10, (500000,3))
t = time.perf_counter()
test1(a)
print ("Test 1 speed:", time.perf_counter() - t)
t = time.perf_counter()
test2(a)
print ("Test 2 speed:", time.perf_counter() - t)
- Тест 1 скорость: 3,261552719 с
- Скорость теста 2: 0,716983475 с
Комментарии:
1. Они не являются двумя формами синтаксиса для одного и того же; первый код создает объект массива numpy на каждой итерации, в то время как второй код этого не делает.
2. Для меня выделяются две причины: 1) выделение памяти для создаваемых массивов numpy (как отметил @mkrieger1); 2) накладные расходы на итерацию по очень небольшим коллекциям (см. Развертывание цикла )
3. Массивы NumPy оптимизированы для больших массивов. Операции имеют высокие накладные расходы на каждый вызов, но очень низкие накладные расходы на каждый элемент. У вас есть 3 элемента. Накладные расходы на каждый вызов преобладают.
4. Это пошло бы быстрее, если бы вы не делали зацикливание вручную
coords
.5. В качестве эксперимента я увеличил время с 3,3 секунды в тесте 1 до 2,4, просто выйдя
mins
за пределы функции, чтобы исключить это для создания массива циклов. Более тонко,for coord in coords:
имеет столько же созданий массива — по одному для каждогоcoord
, извлеченного из массива. Если бы вы могли просто удалить это, у вас было бы меньше 1,5 секунд. Обращение к отдельным элементам массива обходится дорого, потому что вам приходится преобразовывать каждый доступ в python float. Таким образом, векторизованное решение выигрывает в большинстве случаев.
Ответ №1:
Операции Python и распределение памяти, как правило, намного медленнее, чем высоко оптимизированные, векторизованные операции с массивами Numpy. Поскольку вы зацикливаетесь на массиве и выделяете память, вы не получаете никаких преимуществ, которые предлагает Numpy. Это особенно плохо в вашем первом случае, потому что это приводит к чрезмерному количеству выделений небольших массивов.
Сравните свой код с тем, который выгружает все операции в Numpy вместо того, чтобы Python выполнял операции одну за другой:
def test3(coords):
mins = (coords - 1)**2
results = np.sqrt(np.sum(mins, axis=1))
return results
В моей системе это приводит к:
Test 1 speed: 4.995761550962925
Test 2 speed: 1.3881473205983639
Test 3 speed: 0.05562112480401993
Комментарии:
1. На самом деле вам это не нужно
tile
— вещание справится с этим.2. Вау, это очень полезно. Я был в курсе векторизованных операций, но я понятия не имею, почему я никогда не рассматривал возможность проведения операций таким образом.
3. @JacobBumgarner — это основа numpy и проектов, которые его используют. Используйте основные типы данных C/Fortran и применяйте операции над всем массивом. Типы данных Python все громоздки по сравнению с ними.
4. @tdelaney Спасибо за вашу помощь. Я чувствую, что некоторые из этих вещей должны были быть очевидны для меня, но лучше научиться этому сейчас, чем никогда.
5. @JacobBumgarner — Не волнуйся. Это совсем не очевидно.