Почему назначения переменных выполняются быстрее, чем вызовы из массивов в python?

#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 — Не волнуйся. Это совсем не очевидно.