numpy - (np.add(X, Y, out=X); np.add(X, Y, out=X)) over np.add(X, 2*Y, out=X)

Viewed 101

Question

np.add(X, 2*Y, out=X) is so slower than np.add(X, Y, out=X); np.add(X, Y, out=X). Is it an actual practice to use np.add(X, Y, out=X); np.add(X, Y, out=X) in the real world case to gain the performance? I have not done a massive numerical computation in numpy, hence would like to know from those who actually working on problems where such difference can be crutial.

I suppose it is, but any other way to make the statement simple while achieving the performance?

Background

Temporary copy demonstrates the impact of temporary copy.

Problem is that there are many other cases where such copies needs to be created and this impact the performance...

  • code is not the same from those in the reference.
import numpy as np
import timeit

setup = """
import numpy as np

X = np.ones(100000, dtype=np.int)
Y = np.ones(100000, dtype=np.int)
"""
iterations = 100000
elepased = timeit.timeit(
    stmt="np.add(X, 2*Y, out=X)",
    setup=setup,
    number=iterations
)
elepased / iterations * 1e6  

84.75939844996901

elepased = timeit.timeit(
    stmt="np.add(X, Y, out=X); np.add(X, Y, out=X)",
    setup=setup,
    number=iterations
)
elepased / iterations * 1e6  

57.947089899971616

Environment

Running in a jupyter notebook on Ubuntu 20.04LTS, 1CPU 8cores

  • numpy 1.19.2
  • Python 3.8.5 (default, Sep 4 2020, 07:30:14) [GCC 7.3.0]
1 Answers

I believe the boost in speed specifically comes from the read and write operations on new memory by the operation 2*Y. I have added another scenario where you simply overwrite Y by adding it on itself, which is a bit faster as well.

Case 1:

X, Y, 2Y have to be in memory and have their corresponding read and writes.

Y = np.ones((10000,))
X = np.zeros((10000,))

#Case 1:
np.add(X, 2*Y, out=X) #Double Y, store somewhere, and add to X
13.3 µs ± 929 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

Case 2:

Only X and Y are in memory, but X has multiple write operations.

Y = np.ones((10000,))
X = np.zeros((10000,))

#Case 2:
np.add(X, Y, out=X) #Add to X
np.add(X, Y, out=X) #Add to X again
12.8 µs ± 830 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

Case 3 (Fastest):

Only X and Y are in memory, but X has one write operation and Y has one write operation.

Y = np.ones((10000,))
X = np.zeros((10000,))

#Case 3:
np.add(Y, Y, out=Y) #double Y
np.add(X, Y, out=X) #then add to X
12.2 µs ± 136 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

Checking memory shared -

#2*Y is a brand new array in memory
print(np.shares_memory(Y, 2*Y))

#np.add(Y, Y, out=Y) Adding Y to itself shares memory
print(np.shares_memory(Y, np.add(Y, Y, out=Y)))
False
True
Related