Skip to content

PERF: Skip probing __array_ufunc__ for NumPy builtin scalars #21470

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
May 11, 2022

Conversation

eendebakpt
Copy link
Contributor

The method PyUFuncOverride_GetNonDefaultArrayUfunc is expensive on numpy scalars because these objects do not have a __array_ufunc__ set and for a missing attribute lookup cpython generates an exception that is later cleared by numpy. This is a performance bottleneck, see #21455.

An issue has been submitted to cpython (python/cpython#92216). But even if this is addressed in cpython, it will take untill python 3.12+ before this will be useable by numpy.

As an alternative solution, this PR adds a fast path to PyUFuncOverride_GetNonDefaultArrayUfunc to determine whether an object is a numpy scalar. Some remarks:

  • Currently the check is only on PyDoubleArrType_Type and PyIntArrType_Type. Perhaps we can check all types easily? Or select a different subset of all numpy scalar types.
  • Subclasses of the numpy scalars are not handled
  • An alternative would be to add __array_ufunc__ to the numpy scalars, but this is not backwards compatible (PERF: Improve performance of special attribute lookups #21423 (comment))

Microbenchmark on np.sqrt(np.float64(1.1)):

main: 0.54
PR: 0.40
Full benchmark
import numpy as np
import math
import time
print(np.__version__)

w=np.float64(1.1)
#w=1.1

niter=600_000
for kk in range(3):
    t0=time.perf_counter()
    for ii in range(niter):
        _=np.sqrt(w)
    dt=time.perf_counter()-t0
    print(f'loop {kk}: {dt}')

@seberg
Copy link
Member

seberg commented May 7, 2022

We also already have PyArray_CheckAnyScalarExact, which is already used in binop.h as well. So, yes, I think it is fine to do this.

EDIT: Of course, it might be interesting to see if using that function is much slower than the other one.

@eendebakpt
Copy link
Contributor Author

eendebakpt commented May 8, 2022

We also already have PyArray_CheckAnyScalarExact, which is already used in binop.h as well. So, yes, I think it is fine to do this.

EDIT: Of course, it might be interesting to see if using that function is much slower than the other one.

I found is_anyscalar_exact, which works just as fast so I am using that one now.

Copy link
Member

@seberg seberg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good now, thanks! Just those two small nits to clean up the code a bit.

Some of the scalar benchmarks should notice this now, so I think it is OK benchmark wise. Although adding new small-array benchmarks would be cool anyway.
(Or maybe as Matti suggested there, see if we shouldn't just add small arrays to the shape matrix.)

eendebakpt and others added 3 commits May 9, 2022 11:31
@seberg seberg changed the title PERF [RFC] Fast check on numpy scalars in PyUFuncOverride_GetNonDefaultArrayUfunc PERF: Skip probing __array_ufunc__ for NumPy builtin scalars May 11, 2022
@seberg
Copy link
Member

seberg commented May 11, 2022

Thanks @eendebakpt lets put this in.

@seberg seberg merged commit 7987557 into numpy:main May 11, 2022
@eendebakpt eendebakpt deleted the fast_check_attribute branch May 12, 2022 08:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants