I saw in another SO thread that it's possible to create a single-element view of an array arr
with arr[index:index+1]
. This is useful to me since I need to set several values of a (possibly large ~100k entries) array repeatedly. But before I just used that approach, I wanted to make sure that extra work of creating the view doesn't cost too much time.
Surprisingly, I've found that if you access an index at least ~10 times, you're already better off using the view.
The data for this plot was created by timing the two aproaches (in python 3.10):
#!/bin/python3
# https://gist.github.com/SimonLammer/7f27fd641938b4a8854b55a3851921db
from datetime import datetime, timedelta
import numpy as np
import timeit
np.set_printoptions(linewidth=np.inf, formatter={'float': lambda x: format(x, '1.5E')})
def indexed(arr, indices, num_indices, accesses):
s = 0
for index in indices[:num_indices]:
for _ in range(accesses):
s += arr[index]
def viewed(arr, indices, num_indices, accesses):
s = 0
for index in indices[:num_indices]:
v = arr[index:index+1]
for _ in range(accesses):
s += v[0]
return s
N = 11_000 # Setting this higher doesn't seem to have significant effect
arr = np.random.randint(0, N, N)
indices = np.random.randint(0, N, N)
options = [1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946]
for num_indices in options:
for accesses in options:
print(f"{num_indices=}, {accesses=}")
for func in ['indexed', 'viewed']:
t = np.zeros(5)
end = datetime.now() + timedelta(seconds=2.5)
i = 0
while i < 5 or datetime.now() < end:
t += timeit.repeat(f'{func}(arr, indices, num_indices, accesses)', number=1, globals=globals())
i += 1
t /= i
print(f" {func.rjust(7)}:", t, f"({i} runs)")
These observations are very counterintuitive to me.
Why is viewed
faster than indexed
(for more than 10 accesses per index)?
Edit 1:
- gist crossreference: https://gist.github.com/SimonLammer/7f27fd641938b4a8854b55a3851921db
- r/Numpy crossreference: https://www.reddit.com/r/Numpy/comments/wb4p12/why_is_repeated_numpy_array_access_faster_using_a/
Edit 2:
I could replicate Jérôme Richard's findings. The culprit is the index datatype (python int vs. numpy int):
>>> import timeit
>>> timeit.timeit('arr[i]', setup='import numpy as np; arr = np.random.randint(0, 1000, 1000); i = np.random.randint(0, len(arr), 1)[0]', number=20000000)
1.618339812999693
>>> timeit.timeit('arr[i]', setup='import numpy as np; arr = np.random.randint(0, 1000, 1000); i = np.random.randint(0, len(arr), 1)[0]; i = int(i)', number=20000000)
1.2747555710002416