numexpr
модуль обеспечивает очень простую и при этом эффективную для памяти среду , которую можно использовать здесь. Это автоматически заботится о переполнении при выполнении арифметических операций. Давайте рассмотрим пример и посмотрим, как решить нашу проблему -
In [63]: a = np.array([3,252,89],dtype=np.uint8)
...: b = np.array([10,255,19],dtype=np.uint8)
In [64]: import numexpr as ne
In [65]: ne.evaluate('abs(a-b)')
Out[65]: array([ 7., 3., 70.])
Следовательно, чтобы получить желаемый результат -
In [66]: int(ne.evaluate('sum(abs(a-b))'))
Out[66]: 80
Сравнение с обновленной версией NumPy -
In [67]: np.sum(np.abs(a.astype(np.int16) - b.astype(np.int16)))
Out[67]: 80
Эффективность памяти
Теперь давайте расширимся до очень большого массива и выясним суть проблемы, которая заключается в эффективности памяти. Мы будем использовать memory_profiler
модуль, чтобы проверить то же самое.
Сценарий Python с версиями NumPy и numexpr
, перечисленными как numpy_numexpr_memeff.py
-
import numpy as np
import numexpr as ne
from memory_profiler import profile
np.random.seed(0)
a = np.random.randint(0,256,(1000000))
b = np.random.randint(0,256,(1000000))
@profile(precision=10)
def numpy1():
return np.sum(np.abs(a.astype(np.int16) - b.astype(np.int16)))
@profile(precision=10)
def numexpr():
return int(ne.evaluate('sum(abs(a-b))'))
if __name__ == '__main__':
numpy1()
if __name__ == '__main__':
numexpr()
Результаты запуска сценария из командной строки -
$ python -m memory_profiler numpy_numexpr_memeff.py
Filename: numpy_numexpr_memeff.py
Line # Mem usage Increment Line Contents
================================================
9 63.0468750000 MiB 0.0000000000 MiB @profile(precision=10)
10 def numpy1():
11 65.3437500000 MiB 2.2968750000 MiB return np.sum(np.abs(a.astype(np.int16) - b.astype(np.int16)))
Filename: numpy_numexpr_memeff.py
Line # Mem usage Increment Line Contents
================================================
13 65.3437500000 MiB 0.0000000000 MiB @profile(precision=10)
14 def numexpr():
15 65.5859375000 MiB 0.2421875000 MiB return int(ne.evaluate('sum(abs(a-b))'))
Итак, кажется, numexpr
версия занимает 1/10 памяти по сравнению с NumPy.
Производительность
Сроки -
In [68]: np.random.seed(0)
...: a = np.random.randint(0,256,(1000000))
...: b = np.random.randint(0,256,(1000000))
In [71]: %timeit np.sum(np.abs(a.astype(np.int16) - b.astype(np.int16)))
3.99 ms ± 88.3 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [72]: %timeit int(ne.evaluate('sum(abs(a-b))'))
4.71 ms ± 112 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Итак, с точки зрения производительности, версия numexpr
близка, но не так хороша, как версия NumPy.
Другой может быть использовать тот факт, что если мы введем один увеличенный, другой будет автоматически увеличен при выполнении арифметических операций. Итак, мы могли бы просто сделать -
np.sum(np.abs(a.astype(np.int16) - b))
Python-скрипт для проверки эффективности памяти для этого, как numpys_memeff.py
-
import numpy as np
from memory_profiler import profile
np.random.seed(0)
a = np.random.randint(0,256,(1000000))
b = np.random.randint(0,256,(1000000))
@profile(precision=10)
def numpy1():
return np.sum(np.abs(a.astype(np.int16) - b.astype(np.int16)))
@profile(precision=10)
def numpy2():
return np.sum(np.abs(a.astype(np.int16) - b))
if __name__ == '__main__':
numpy1()
if __name__ == '__main__':
numpy2()
Результаты -
$ python -m memory_profiler numpys_memeff.py
Filename: numpys_memeff.py
Line # Mem usage Increment Line Contents
================================================
8 56.6015625000 MiB 0.0000000000 MiB @profile(precision=10)
9 def numpy1():
10 59.1210937500 MiB 2.5195312500 MiB return np.sum(np.abs(a.astype(np.int16) - b.astype(np.int16)))
Filename: numpys_memeff.py
Line # Mem usage Increment Line Contents
================================================
12 59.1210937500 MiB 0.0000000000 MiB @profile(precision=10)
13 def numpy2():
14 59.3632812500 MiB 0.2421875000 MiB return np.sum(np.abs(a.astype(np.int16) - b))
По производительности кажется тоже немного лучше -
In [68]: np.random.seed(0)
...: a = np.random.randint(0,256,(1000000))
...: b = np.random.randint(0,256,(1000000))
In [71]: %timeit np.sum(np.abs(a.astype(np.int16) - b.astype(np.int16)))
3.99 ms ± 88.3 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [73]: %timeit np.sum(np.abs(a.astype(np.int16) - b))
3.84 ms ± 29.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)