Почему numpy.where намного быстрее, чем альтернативы

#python #performance #numpy

#python #Производительность #numpy

Вопрос:

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

 import time
import numpy as np
np.random.seed(10)
b=np.random.rand(10000,1000)
def f(a=1):
    tott=0
    for _ in range(a):
        q=np.array(b)
        t1 = time.time()
        for i in range(len(q)):
            for j in range(len(q[0])):
                if q[i][j]>0.5:
                    q[i][j]=1
                else:
                    q[i][j]=-1
        t2=time.time()
        tott =t2-t1
    print(tott/a)
  

Как вы можете видеть, в основном функция заключается в повторении в двойном цикле. Итак, я попытался использовать np.nditer , np.vectorize и map вместо этого. If дает некоторое ускорение (например, в 4-5 раз, за исключением np.nditer ), но! с np.where(q>0.5,1,-1) ускорением почти в 100 раз.
Как я могу выполнять итерации по массивам numpy так быстро, как np.where это делает? И почему это намного быстрее?

Ответ №1:

Это потому, что ядро numpy реализовано на C. Вы в основном сравниваете скорость C с Python.

Если вы хотите использовать преимущество numpy в скорости, вам следует выполнять как можно меньше вызовов в вашем коде Python. Если вы используете цикл Python, вы уже проиграли, даже если вы используете функции numpy только в этом цикле. Используйте функции более высокого уровня, предоставляемые numpy (вот почему они поставляют так много специальных функций). Внутренне он будет использовать гораздо более эффективный цикл (C-)

Вы можете самостоятельно реализовать функцию на C (с циклами) и вызвать ее из Python. Это должно дать сопоставимые скорости.

Комментарии:

1. Если вы используете стандартный интерпретатор python (CPython), сам python написан на C. Дело в том, что numpy использует C / Fortran (?) код, который сам по себе оптимизирован. Ключевым моментом здесь является использование векторных операций

2. @user8408080 конечно, в конце концов, все сводится к машинному коду. Однако CPython интерпретируется во время компиляции C (или Fortran). Известно, что Python не является самым быстрым интерпретируемым языком (и он никогда не был разработан таким образом, iirc).

3. Я согласен, что это, возможно, придирка, но на первый взгляд это звучало немного странно. 1 тыс.

Ответ №2:

Чтобы ответить на этот вопрос, вы можете получить ту же скорость (100-кратное ускорение), используя numba библиотеку:

 from numba import njit

def f(b):
    q = np.zeros_like(b)

    for i in range(b.shape[0]):
        for j in range(b.shape[1]):
            if q[i][j] > 0.5:
                q[i][j] = 1
            else:
                q[i][j] = -1

    return q

@njit
def f_jit(b):
    q = np.zeros_like(b)

    for i in range(b.shape[0]):
        for j in range(b.shape[1]):
            if q[i][j] > 0.5:
                q[i][j] = 1
            else:
                q[i][j] = -1

    return q
  

Сравните скорость:

Обычный Python

 %timeit f(b)
592 ms ± 5.72 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
  

Numba (точно в срок скомпилирован с использованием скорости LLVM ~ C)

 %timeit f_jit(b)
5.97 ms ± 105 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)