#python #arrays #numpy #performance
#питон #массивы #тупица #Производительность
Вопрос:
- Почему в этом случае Numpy медленнее, чем понимание списка?
- Каков наилучший способ векторизации этой сеточной конструкции?
In [1]: import numpy as np In [2]: mesh = np.linspace(-1, 1, 3000) In [3]: rowwise, colwise = np.meshgrid(mesh, mesh) In [4]: f = lambda x, y: np.where(x gt; y, x**2, x**3) # Using 2D arrays: In [5]: %timeit f(colwise, rowwise) 285 ms ± 2.25 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) # Using 1D array and list-comprehension: In [6]: %timeit np.array([f(x, mesh) for x in mesh]) 58 ms ± 2.69 ms per loop (mean ± std. dev. of 7 runs, 10 loops each) # Equivalent result In [7]: np.allclose(f(colwise, rowwise), np.array([f(x, mesh) for x in mesh])) True
Комментарии:
1. Вы используете
where
в обоих случаях. Но для [5] все три аргумента имеют форму (3000,3000). В [6], в то времяf
как вызывается 3000 раз,x
аргумент является скалярным. Использованиеf(mesh,x)
замедляет его. С другой стороны, использованиеsparse=True
inmeshgrid
ускорит [5], потомуx
что arg составляет всего (3000,).2.
where
это функция. Его аргументы оцениваются в полном объеме перед передачей ему. где` затем возвращает новый массив, совместимый сbroadcasted
объединением трех массивов. Не думайте об этом как об итераторе.
Ответ №1:
In [1]: In [2]: mesh = np.linspace(-1, 1, 3000) ...: In [3]: rowwise, colwise = np.meshgrid(mesh, mesh) ...: In [4]: f = lambda x, y: np.where(x gt; y, x**2, x**3)
Кроме того, позволяет сделать разреженную сетку:
In [2]: r1,c1 = np.meshgrid(mesh,mesh,sparse=True) In [3]: rowwise.shape Out[3]: (3000, 3000) In [4]: r1.shape Out[4]: (1, 3000)
С разреженной сеткой время даже лучше, чем ваша итерация:
In [5]: timeit f(colwise, rowwise) 645 ms ± 57.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) In [6]: timeit f(c1,r1) 108 ms ± 3.85 ms per loop (mean ± std. dev. of 7 runs, 10 loops each) In [7]: timeit np.array([f(x, mesh) for x in mesh]) 166 ms ± 13.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
Другой ответ подчеркивает кэширование. Другие сообщения показали, что небольшое количество итераций может быть быстрее, чем работа с очень большими массивами, например, при использовании matmul
. Я не знаю, замедляет ли это кэширование или какое-то другое усложнение управления памятью.
Но в 3000*3000*8
байтах я не уверен, что здесь проблема в этом. Вместо этого я думаю, что это время x**2
, x**3
необходимое для выражений и.
Аргументы функции where
оцениваются перед передачей.
Выражение условия занимает небольшое количество времени:
In [8]: timeit colwisegt;rowwise 24.2 ms ± 71.1 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
Но выражение мощности для массива (3000,3000) занимает большую часть общего времени:
In [9]: timeit rowwise**3 467 ms ± 8.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
Сравните это со временем, необходимым для разреженного эквивалента:
In [10]: timeit r1**3 142 µs ± 150 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
На этот раз в 3288 раз быстрее; это немного хуже, чем масштабирование O(n).
повторное умножение лучше:
In [11]: timeit rowwise*rowwise*rowwise 116 ms ± 12 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
В [f(x, mesh) for x in mesh]
, x**3
работает на скаляре, поэтому работает быстро, даже если это повторяется 3000 раз.
На самом деле, если мы уберем вычисления мощности из времени, весь массив where
будет относительно быстрым:
In [15]: %%timeit x2,x3 = rowwise**2, rowwise**3 ...: np.where(rowwisegt;colwise, x2,x3) 89.8 ms ± 3.99 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
Ответ №2:
Почему в этом случае Numpy медленнее, чем понимание списка?
по сути, вы страдаете от 2 проблем.
первый-использование кэш-памяти, вторая версия использует только подмножество пространства (3000,1) (1,3000), для расчета которой может поместиться красиво в кэш, так что xgt;y, x**2 , x**3
можно все уместить внутри тайник, в котором несколько скоростей вещи, в первом варианте расчета каждого из этих 3 на 3000×3000 массива (9 млн. записей), который никогда не может сидеть внутри кэша (обычно ~ 2-5 мм), то НП., где называется то, что имеет, чтобы получить часть данных из оперативной памяти (а не кэш), чтобы сделать их копирование памяти, которая затем возвращается по кусочкам, чтобы ваша память, которая стоит очень дорого.
также реализация numpy np.где несколько не осознает выравнивания и обращается к вашим массивам по столбцам, а не по строкам, поэтому, по сути, она захватывает каждую запись из вашей оперативной памяти и вообще не использует кэш.
понимание вашего списка фактически решает эту проблему, поскольку оно имеет дело только с небольшим подмножеством данных в данный момент времени, и, следовательно, все данные могут находиться в вашем кэше, но он все еще использует np.где, это только заставляет его использовать доступ по строкам и, следовательно, использовать ваш кэш.
вторая проблема заключается в вычислении x**2
и x**3
, которое представляет собой возведение в степень с плавающей запятой, что очень дорого, рассмотрите возможность его замены на x*x
и x*x*x
Каков наилучший способ векторизации этой сеточной конструкции?
очевидно, вы написали это в своем втором методе.
еще более быстрая, но ненужная оптимизация за счет использования кэша заключается в том, чтобы написать свой собственный код на C и вызвать его из python, чтобы вам не нужно было его оценивать x*x or x*x*x
, если вам это не нужно, и не нужно будет хранить xgt;y,x*x,x*x*x
, но ускорение не будет стоить хлопот.
Комментарии:
1. Объясняет ли кэширование большую скорость использования массивов (3000,1) и (13000)
broadcastable
?2. это зависит от контекста и использования, иногда numpy будет транслировать их как (3000,3000) перед применением текущей операции, поэтому ускорения не будет, однако в целом вам следует ожидать ускорения при использовании меньших массивов для выполнения работы с большими массивами, если вас не замедляет интерпретатор python.
3. В моем тестировании
x**3
на массиве (3000,3000) это основная проблема, с немного худшим масштабированием, чем O(n).