0

Which is a faster method? like aren't they both the same?

start = time.time()
arr = np.array([1,2,3,4,5,6,7,8,9,0,12])
total_price =  np.sum(arr[arr < 7])* 2.14

print(total_price)
print('Duration: {} seconds'.format(time.time() - start))
start = time.time()
arr = np.array([1,2,3,4,5,6,7,8,9,0,12])
total_price =  (arr[arr<7]).sum()* 2.14

print(total_price)
print('Duration: {} seconds'.format(time.time() - start))

On running the code, again and again, both of them give differing resultant execution time. Sometimes the former method is faster and sometimes later.

kush_shah
  • 137
  • 2
  • 9
  • arr.sum implies a numpy array i think ... so its probably faster most of the time (although it might also hook into the normal sum and vectorize that) – Joran Beasley May 21 '20 at 23:32
  • Probably numpy is the faster but since you have the code already, run it a bunch of times and see which one is faster. The hands on experiment will give the ultimate answer. – Gerardo Zinno May 21 '20 at 23:36
  • 6
    If you're going to do benchmarking, don't include `print` in the code being timed. And learn about [`timeit`](https://docs.python.org/3/library/timeit.html). – Mark Ransom May 21 '20 at 23:36
  • 1
    The difference should be ignorable. If the type is known, i prefer `arr.sum()`. I guess `np.sum` is more generic approach if the type does not support `sum`. – Aaron May 21 '20 at 23:38
  • @MarkRansom or even better use cProfile (or %prun) :P if you really want to get nitty gritty with it – Joran Beasley May 21 '20 at 23:38
  • Both are basically the same except that in the first snippet "np.sum" has first to check the type of its parameter (can be neglected). After that the numpy array can be processed in the same way. – Michael Butscher May 21 '20 at 23:38
  • @MichaelButscher they *can* be processed the same way, but are they? Since a numpy array has a well defined memory layout, it can use low-level vector instructions to get a speedup. Don't know if it does or not. – Mark Ransom May 21 '20 at 23:42
  • I I think they are just the same thing. – Quang Hoang May 21 '20 at 23:45

1 Answers1

5

Removing the giant docstring, the code for np.sum is

@array_function_dispatch(_sum_dispatcher)
def sum(a, axis=None, dtype=None, out=None, keepdims=np._NoValue,
        initial=np._NoValue, where=np._NoValue):

    if isinstance(a, _gentype):
        # 2018-02-25, 1.15.0
        warnings.warn(
            "Calling np.sum(generator) is deprecated, and in the future will give a different result. "
            "Use np.sum(np.fromiter(generator)) or the python sum builtin instead.",
            DeprecationWarning, stacklevel=3)

        res = _sum_(a)
        if out is not None:
            out[...] = res
            return out
        return res

    return _wrapreduction(a, np.add, 'sum', axis, dtype, out, keepdims=keepdims,
                          initial=initial, where=where)

array_function_dispatch handles __array_function__ overrides that non-NumPy types might provide, while _wrapreduction is responsible for making sure np._NoValue isn't passed to the underlying implementation, as well as deciding whether to call the sum method (for non-array input) or add.reduce (for array input).

So it does a bunch of checks to handle non-array inputs, then eventually passes the task to np.add.reduce if the input is an array.

Meanwhile, np.ndarray.sum is this:

static PyObject *
array_sum(PyArrayObject *self, PyObject *args, PyObject *kwds)
{
    NPY_FORWARD_NDARRAY_METHOD("_sum");
}

where NPY_FORWARD_NDARRAY_METHOD is a macro that forwards the operation to numpy.core._methods._sum:

def _sum(a, axis=None, dtype=None, out=None, keepdims=False,
         initial=_NoValue, where=True):
    return umr_sum(a, axis, dtype, out, keepdims, initial, where)

and umr_sum is an alias for np.add.reduce.

Both code paths eventually end up at np.add.reduce, but the ndarray.sum code path doesn't involve all the pre-check work for non-array input, because the array already knows it's an array.

In these tests the calculation time itself is small enough that the extensive prechecks make a big difference:

In [607]: timeit np.sum(np.arange(1000))                                                 
15.4 µs ± 42.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
In [608]: timeit np.arange(1000).sum()                                                   
12.2 µs ± 29.9 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
In [609]: timeit np.add.reduce(np.arange(1000))                                          
9.19 µs ± 17.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

numpy has a number of function/method pairs like this. Use which ever is most convenient - and looks prettiest in your code!

user2357112
  • 260,549
  • 28
  • 431
  • 505
hpaulj
  • 221,503
  • 14
  • 230
  • 353
  • Thanx! I hope there's no complex mechanism behind both of the methods. – kush_shah May 24 '20 at 16:13
  • 1
    https://numpy.org/neps/nep-0018-array-function-protocol.html#performance talks about performance of `np.sum` in the current version which adds `@array_function_dispatch` layer. – hpaulj May 24 '20 at 16:38
  • Ever since they added the [`__array_function__` mechanism](https://numpy.org/neps/nep-0018-array-function-protocol.html), the overhead for `np.sum` (and most NumPy functions) has gotten even worse. – user2357112 Feb 24 '23 at 11:07
  • ...wait, `__array_function__` was already in when this answer was posted. The answer just left out the decorator that handles it. – user2357112 Feb 24 '23 at 11:11