4
votes

I noticed that indexing a multi dimensional array takes more time than indexing a single dimensional array

a1 = np.arange(1000000)
a2 = np.arange(1000000).reshape(1000, 1000)
a3 = np.arange(1000000).reshape(100, 100, 100)

When I index a1

%%timeit
a1[500000]

The slowest run took 39.17 times longer than the fastest. This could mean that an intermediate result is being cached. 10000000 loops, best of 3: 84.6 ns per loop

%%timeit
a2[500, 0]

The slowest run took 31.85 times longer than the fastest. This could mean that an intermediate result is being cached. 10000000 loops, best of 3: 102 ns per loop

%%timeit
a3[50, 0, 0]

The slowest run took 46.72 times longer than the fastest. This could mean that an intermediate result is being cached. 10000000 loops, best of 3: 119 ns per loop


At what point should I consider an alternative way to index or slice a multi-dimensional array? What are the circumstances that make it worth the effort and loss of transparency?

1
You are talking more about indexing individual items, not slices (:). Indexing on the flat version is faster - but by a factor less that 2? Is the speed gain worth the loss in clarity? Should you even be indexing a single item?hpaulj
@hpaulj it was me noticing this that led me to wondering "Under what circumstances would it be worth while to flatten an array and index or slice?"piRSquared
stackoverflow.com/questions/28005531/… - selecting several items from each row of 2d array. I found that flat indexing was faster, but had to be balanced against the higher cost of calculating the index. Basically the same sort of pattern - flat is better when the arrays get larger.hpaulj

1 Answers

5
votes

One alternative to slicing an (n, m) array is to flatten the array and derive what it's one dimensional position must be.

consider a = np.array([[0, 1, 2], [3, 4, 5], [6, 7, 8]])
we can get the 2nd row, 3rd column with a[1, 2] and get 5
or we can calculate that 1 * a.shape[1] + 2 is the one dimensional position if we flatten a with order='C'
thus we can perform the equivalent slice with a.ravel()[1 * a.shape[1] + 2]

Is this efficient? No, for indexing a single number from an array, it isn't worth the trouble.

What about if we want to slice many numbers from the array? I devised the following test for a 2-D array


2-D test

from timeit import timeit

n, m = 10000, 10000
a = np.random.rand(n, m)
r = pd.DataFrame(index=np.power(10, np.arange(7)), columns=['Multi', 'Flat'])

for k in r.index:
    b = np.random.randint(n, size=k)
    c = np.random.randint(m, size=k)
    kw = dict(setup='from __main__ import a, b, c', number=100)
    r.loc[k, 'Multi'] = timeit('a[b, c]', **kw)
    r.loc[k, 'Flat'] = timeit('a.ravel()[b * a.shape[1] + c]', **kw)

r.div(r.sum(1), 0).plot.bar()

enter image description here

It appears that when slicing more than 100,000 numbers, it's better to flatten the array.


What about 3-D
3-D test

from timeit import timeit

l, n, m = 1000, 1000, 1000
a = np.random.rand(l, n, m)
r = pd.DataFrame(index=np.power(10, np.arange(7)), columns=['Multi', 'Flat'])

for k in r.index:
    b = np.random.randint(l, size=k)
    c = np.random.randint(m, size=k)
    d = np.random.randint(n, size=k)

    kw = dict(setup='from __main__ import a, b, c, d', number=100)
    r.loc[k, 'Multi'] = timeit('a[b, c, d]', **kw)
    r.loc[k, 'Flat'] = timeit('a.ravel()[b * a.shape[1] * a.shape[2] + c * a.shape[1] + d]', **kw)

r.div(r.sum(1), 0).plot.bar()

enter image description here

Similar results, maybe more dramatic.

Conclusion
For 2 dimensional arrays, consider flattening and deriving flatten positions if you need to pull more than 100,000 elements from the array.

For 3 or more dimensions, it seems clear that flattening the array is almost always better.


Criticism is welcome
Did I do something wrong? Did I not think of something obvious?