8000 MAINT: Remove internal uses of _inspect module by rossbar · Pull Request #16787 · numpy/numpy · GitHub
[go: up one dir, main page]

Skip to content

MAINT: Remove internal uses of _inspect module #16787

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

Closed
wants to merge 6 commits into from

Conversation

rossbar
Copy link
Contributor
@rossbar rossbar commented Jul 9, 2020

Refactor functions in np.ma.core and np.core.overrides to replace uses of functions from the compat._inspect module to the standard inspect module.

Note in both cases I chose to use the class-based inspect.Signature API to replace uses of the deprecated inspect.getargspec. If it is preferred, the use of the Signature could be replaced instead with usage of inspect.getfullargspec, which is more similar to the original.

@eric-wieser
Copy link
Member
eric-wieser commented Jul 9, 2020

xref #16299, which contains a similar but IMO clearer implementation in a comment.

@charris
Copy link
Member
charris commented Jul 9, 2020

How much does this affect import times?

@rossbar
Copy link
Contributor Author
rossbar commented Jul 9, 2020

How much does this affect import times?

It should be negligible as importing inspect was already added in #16311.

xref #16299, which contains a similar but IMO clearer implementation in a comment.

Thanks @eric-wieser - I had missed the code in the issue. I will take a look

@rossbar
Copy link
Contributor Author
rossbar commented Jul 9, 2020

@eric-wieser - agreed, your implementation in #16299 is much more clear: updating now.

@charris
Copy link
Member
charris commented Jul 15, 2020

@eric-wieser Ping.

@mattip mattip requested a review from eric-wieser August 2, 2020 13:37
rossbar and others added 6 commits August 3, 2020 13:21
Replace usage of numpy.compat._inspect with implementation
based on the standard inspect module.
Replace usage of numpy.compat._inspect with implementation
based on the standard inspect module.
Use the parameter replacement mechansism for inspect.signature to
verify consistent signatures between the dispatcher and primary
function implementation.

Co-authored-by: Eric Wieser <wieser.eric@gmail.com>
@rossbar rossbar force-pushed the maint/rm_compat_inspect branch from 7799bf1 to 6e35c2a Compare August 3, 2020 20:58
eric-wieser
eric-wieser previously approved these changes Aug 3, 2020
Copy link
Member
@eric-wieser eric-wieser left a comment

Choose a reason for hiding this comment

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

Code looks good, but maybe worth benchmarking with python - X importtime just to be sure.

Remember, this dispatcher checking runs at import time.

@mattip
Copy link
Member
mattip commented Aug 14, 2020

There is a significant increase in import time, from 62ms to 74ms:

git checkout 2283e265990ba64 # the commit this branch is based on
git clean -xfd
python setup.py develop
# run twice to create *.pyc files
python -X importtime -c "import numpy" 2>&1 |tee /tmp/pre.txt
python -X importtime -c "import numpy" 2>&1 |tee /tmp/pre.txt
git checkout rm_compat_inspect
git clean -xfd
python setup.py develop
# run twice to create *.pyc files
python -X importtime -c "import numpy" 2>&1 |tee /tmp/post.txt
python -X importtime -c "import numpy" 2>&1 |tee /tmp/post.txt

post.txt
pre.txt

@eric-wieser eric-wieser dismissed their stale review August 14, 2020 07:36

Timings are concerning

@rossbar
Copy link
Contributor Author
rossbar commented Aug 17, 2020

Thanks @mattip , there is indeed a significant increase in import time. I was looking at the posted timings with tuna and noticed that at least some of the increase appears to be due to imports of numpy.compat in numpy.core._type_aliases.

image

I'm not clear on why this is happening (perhaps a change in how the imports are being cached?). The rest of the difference seems like it may come down to the new implementation numpy.core.overrides.verify_matching_signatures being slower than the original version. I'd like to keep pushing on this but am not too familiar with the import system, so if anyone has any tips related to profiling imports or ideas of things to try, please share them!

if dispatcher_spec.defaults != (None,) * len(dispatcher_spec.defaults):
raise RuntimeError('dispatcher functions can only use None for '
'default argument values')
implementation_sig = inspect.signature(implementation)
Copy link
Member

Choose a reason for hiding this comment

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

I can only assume the timings are bad because inspect.signature is slower than getargspec

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, some (very rough) profiling indicates that this is the case. Using the following setup:

from numpy.core.overrides import verify_matching_signatures                     
                                                                                
def foo(                                                                        
    a, b, m, n, k, copy=False, color="red", normed=False, weights=None,         
    bins=10, density=None, size=1024, shape=(3, 4)                              
):                                                                              
    pass                                                                        
                                                                                
def _foo_dispatcher(                                                            
    a, b, m, n, k, copy=None, color=None, normed=None, weights=None,            
    bins=None, density=None, size=None, shape=None                              
):                                                                              
    pass

timeit gives:

On 6e35c2a (this PR):

In [2]: %timeit verify_matching_signatures(foo, _foo_dispatcher)                             
73.1 µs ± 879 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

On 2283e26 (root of branch):

In [2]: %timeit verify_matching_signatures(foo, _foo_dispatcher)                             
4.85 µs ± 11.4 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

So the implementation based on inspect.signature is about 15x slower than the original based on _inspect.getargspec.

One idea I had was to try to re-implement this with inspect.getfullargspec, which is the more full-featured extension of the original (now deprecated) inspect.getargspec. I will update with -X importtime & profiling info from the attempt.

Copy link
Member
@eric-wieser eric-wieser Aug 28, 2020

Choose a reason for hiding this comment

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

One idea I had was to try to re-implement this with inspect.getfullargspec, which is the more full-featured extension of the original (now deprecated) inspect.getargspec.

This is implemented in terms of inspect.signature, then does some extra processing. You'd do better to probe the __code__ object directly.

Perhaps the comparison is the slow part.

@eric-wieser
Copy link
Member

For reference:

In [60]: %timeit inspect.getfullargspec(_foo_dispatcher)
75.2 µs ± 1.77 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [61]: %timeit inspect.signature(_foo_dispatcher)
56 µs ± 1.41 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [63]: %timeit numpy.compat._inspect.getargspec(_foo_dispatcher)
9.33 µs ± 1.98 µs per loop (mean ± std. dev. of 7 runs, 100000 loops each)

@rossbar
Copy link
Contributor Author
rossbar commented Aug 28, 2020

Hmm, so it seems like a slowdown is inevitable by switching to the inspect module, at least if the signature verification is run at import time. This may be a silly question, but does the signature verification need to be run at import time?

@eric-wieser
Copy link
Member

You can shave off a little time by poking at inspect internals:

In [67]: %timeit inspect._signature_from_function(inspect.Signature, _foo_dispatcher)
50.9 µs ± 1.29 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

@eric-wieser
Copy link
Member

This may be a silly question, but does the signature verification need to be run at import time?

If the decorators are for internal use only, I suppose we could do it as a unit test instead?

@shoyer, were these decorators intended for public consumption?

@shoyer
Copy link
Member
shoyer commented Aug 28, 2020

This may be a silly question, but does the signature verification need to be run at import time?

If the decorators are for internal use only, I suppose we could do it as a unit test instead?

Indeed these decorators are for internal use only.

The signature verification could certainly be done in unit tests only. When I tested it originally, it did not have a material effect on import times so I didn't bother.

Base automatically changed from master to main March 4, 2021 02:04
@mattip
Copy link
Member
mattip commented Mar 10, 2022

@rossbar will you be picking this up again?

@rossbar
Copy link
Contributor Author
rossbar commented Mar 10, 2022

As it stands this would increase import time which is a blocker. The next step would be to investigate getting rid of the signature verification at import time, but I don't plan on getting into that in this PR so I'll close it.

@rossbar rossbar closed this Mar 10, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants
0