Подтвердить что ты не робот

Пункт номер быстрее, чем оператор []

У меня есть следующий код в python, который по крайней мере для меня производит странные результаты:

import numpy as np
import timeit

a = np.random.rand(3,2)

print timeit.timeit('a[2,1] + 1', 'from __main__ import a', number=1000000)
print timeit.timeit('a.item((2,1)) + 1', 'from __main__ import a', number=1000000)

Это дает результат:

0.533630132675
0.103801012039

Кажется, хорошо, если я только пытаюсь получить доступ к элементу numpy, но при увеличении этого элемента тайминга становятся странными... Почему существует такая разница в таймингах?

4b9b3361

Ответ 1

В этом случае они не возвращают совершенно то же самое. a[2,1] возвращает a numpy.float64, а a.item((2,1)) возвращает собственный плагин python.

Скалярные vs numpy (float, int и т.д.)

A numpy.float64 скаляр не совсем тождествен с нативным питоном float (они ведут себя одинаково, однако). Простые операции над одним элементом будут быстрее с использованием собственного плавающего python, так как там меньше косвенности. Посмотрите docstring для ndarray.item немного подробнее.

В качестве примера разницы в скорости рассмотрим следующее:

In [1]: x = 1.2

In [2]: y = np.float64(1.2)

In [3]: %timeit x + 1
10000000 loops, best of 3: 58.9 ns per loop

In [4]: %timeit y + 1
1000000 loops, best of 3: 241 ns per loop

Вначале я неправильно указал, что вторым фактором было то, что a.item(...) был немного быстрее, чем a[...]. Это на самом деле не так. Время, затрачиваемое на a.item преобразование скаляра numpy в собственный скаляр python, перегружает время, необходимое для дополнительной логики в a[...]/a.__getitem__(...).


Не обобщайте этот результат более чем на один элемент

Однако вы должны быть осторожны, пытаясь обобщить, что происходит с числовыми скалярами, с тем, как работают массивы numpy в целом. Если вы делаете много индексирования одного элемента в numpy, это обычно анти-шаблон.

Например, сравните:

In [5]: a = np.random.rand(1000)

In [6]: %timeit a + 1
100000 loops, best of 3: 2.32 us per loop

Независимо от того, что мы делаем, мы не сможем сопоставить скорость (или значительно меньшее использование памяти) векторизованной версии (a + 1) выше:

In [7]: %timeit [x + 1 for x in a]
1000 loops, best of 3: 257 us per loop

In [8]: %timeit [a.item(i) + 1 for i in range(len(a))]
1000 loops, best of 3: 208 us per loop

Это связано с тем, что повторение через ndarray происходит медленнее, чем повторение через список. Для вполне справедливого сравнения, переведите все в список собственных плавающих python:

In [9]: b = a.tolist()

In [10]: type(b[0])
Out[10]: float

In [11]: %timeit [x + 1 for x in b]
10000 loops, best of 3: 69.4 us per loop

Очевидно, что с помощью векторизованных операций (первый случай) выполняется намного быстрее, когда вы работаете с большими массивами. Это также намного эффективнее с точки зрения памяти, так как list требует хранения указателей на каждый элемент, а ndarray - в памяти.