Ufunc calls on scalars are very slow
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 32.8k
- Forks
- 12.8k
- Avg merge
- 1d 7h
- Merged PRs (30d)
- 197
Description
It is well known that ufunc calls on scalars are rather slow, but it is probably good to have a summary of why it is so slow, for which it is useful to go along the ufunc_generic_call path. I got only partway, but one possible solution might be for the scalars to already get overridden in in CheckOverride, i.e., to treat them as if they had their own __array_ufunc__ (with priority even below that of ndarray; an actual __array_ufunc__ calling math is slightly slower than our present state).
PyUFunc_CheckOverride: for non-arrays (thus including scalars), this checks whether the scalar has__array_ufunc__. Easy to avoid if our whole API is available - needs #10915.PyUFunc_GenericFunction: to be done (will edit).make_full_arg_tuple: EDIT now fast (with #11231)._find_array_wrap->_find_array_method: skips arrays and scalars, so should be reasonably fast (though a subclass check forGenericis done before type checks on python objects inPyArray_IsAnyScalar(inndarrayobject.h).
Simple timings
Single-input ufunc, comparing with math
a = 1.
a64 = np.float64(1.)
as64 = np.array(1., dtype=np.float64)
%timeit math.sin(a) # and a64, as64
# 76, 76, 87 ns for a, a64, as64
%timeit np.sin(a)
# 600, 930, 450 ns for a, a64, as64
Somewhat more random, for addition
%timeit np.add(1., 1)
# 1000000 loops, best of 3: 970 ns per loop
%timeit 1. + 1
# 100000000 loops, best of 3: 8.73 ns per loop
# slightly fairer
%timeit operator.add(1., 1)
# 10000000 loops, best of 3: 80.4 ns per loop
# Oddly, again, scalars are much slower than array scalars
a64 = np.float64(1.)
%timeit np.add(a64, a64)
# 1000000 loops, best of 3: 1.35 µs per loop
as64 = np.array(1., dtype=np.float64)
%timeit np.add(as64, as64)
# 1000000 loops, best of 3: 468 ns per loop
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Trace the ufunc_generic_call path, beginning with PyUFunc_CheckOverride and PyUFunc_GenericFunction, and review the notes about make_full_arg_tuple and _find_array_method. Compare the scalar timings in the issue with array and math operations; done requires an agreed approach that reduces scalar-call overhead and demonstrates the improvement with comparable timings.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- c, python
- Domain
- performance
- Issue type
- Refactor
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 30/100