-
Notifications
You must be signed in to change notification settings - Fork 24
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
performance of is_type
#86
Comments
Hey @ggoretkin-bdai! If you would like the best performance, the simplest way is to only use so-called faithful types. I'm actually thinking that Here's a small benchmark: In [20]: from plum import dispatch, Val, clear_all_cache
In [21]: @dispatch
...: def f(x: Val[1]):
...: pass
...:
In [22]: x = Val(1)
In [23]: %timeit f(x) # Not faithful...
5.12 µs ± 106 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
In [24]: Val.__faithful__ = True
In [25]: clear_all_cache()
In [26]: %timeit f(x) # Faithful!
554 ns ± 9.35 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each) Would you be able to try this on your end, to see if this works and things still run correctly? |
That would be great! I will give this a shot and report back. For now, I have a few questions. def is_faithful(type_, for_all_x):
for x in for_all_x:
if isinstance(x, type_) == issubclass(type(x), type_):
pass
else:
return False
return True
|
Perhaps the definition is better understood in terms of natural language. Consider, for example, What faithful measures is whether taking The case of |
Thanks for the explanation, @wesselb . I gave your suggestion a try, and I did not notice a significant performance difference. I extracted a minimal example here:
|
Hey @ggoretkin-bdai! Ah... I think my benchmark that I posted above is incorrect. By default In [1]: from plum import dispatch, Val, clear_all_cache
In [2]: @dispatch
...: def f(x: Val[1]):
...: pass
...:
In [3]: x = Val(1)
In [4]: %timeit f(x)
571 ns ± 7.19 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
In [5]: %timeit f(Val(1))
38.2 µs ± 411 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)
In [6]: %timeit Val(1)
36.5 µs ± 539 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each) Currently, In [7]: val = {1: Val(1)}
In [8]: %timeit f(val[1])
663 ns ± 36 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each) |
The ultimate use case I (currently) care about is the one in benchmark: defining |
How about something like this? In [1]: from plum import dispatch, Val
In [2]: _cache = {}
In [3]: def convert(t, x):
...: try:
...: val = _cache[t]
...: except KeyError:
...: _cache[t] = Val(t)
...: val = _cache[t]
...: return _convert(val, x)
...:
In [4]: class A:
...: def __init__(self, x):
...: self.x = x
...:
In [5]: class B:
...: def __init__(self, x):
...: self.x = x
...:
In [6]: @dispatch
...: def _convert(t: Val[A], x: B):
...: return A(x.x)
...:
In [7]: @dispatch
...: def _convert(t: Val[B], x: A):
...: return B(x.x)
...:
In [8]: convert(A, B(1))
Out[8]: <__main__.A at 0x7f5cc6728b50>
In [9]: %timeit convert(A, B(1))
1.88 µs ± 65.4 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
In [10]: %timeit A(B(1).x)
592 ns ± 26 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each) You might be able to squeeze out a little more performance, but this looks pretty good to me already. |
With the caching you suggest, for the benchmark I was using, Plum is now only 7x slower, versus 50x slower: ggoretkin-bdai/investigate-plum-performance@e07f658 A couple comments
I am now trying to see how I can avoid annotating the from/to types twice: @conversion_method(type_from=SpatialMath_SE3, type_to=BD_SE3Pose) # type: ignore[no-redef]
def convert_whatever(from_: SpatialMath_SE3) -> BD_SE3Pose: # noqa: F811
return BD_SE3Pose(from_.data5) I need them annotated in the type hints, but would like to avoid annotating them in the decorator, as is achieved via: Line 260 in 80b614f
|
Here is an attempt that appears to work: by defining def conversion_method_from_signature(f):
"""Decorator to add a conversion method to convert an object from one
type to another.
Like `plum.conversion_method` but the arguments:
type_from (type): Type to convert from.
type_to (type): Type to convert to.
are extracted from the type annotations
"""
signature = plum.extract_signature(f)
[type_from] = signature.types
type_to = signature.return_type
plum.promotion.add_conversion_method(type_from, type_to, f) |
@ggoretkin-bdai, to answer your questions:
Your |
In am interested in reducing the performance penalty of using
plum
in my application vs dispatching "manually" with anif/elif isinstance
chain. Profiling (flamegraph below) reveals a majority of time spent inis_type
:plum/plum/parametric.py
Lines 282 to 298 in b38a162
For my specific microbenchmark,
is_type
is only ever called with ONE value, aplum.parametric.Val[example_py_multiple_dispatch.zoo.MyType]()
.Any ideas for improving the performance? Are there any correctness issues with memoizing the result of
is_type
? I see that there is memoization happening at lower levels via https://github.com/beartype/beartype/blob/49560b8d4d788d279a02283d8da995895440554c/beartype/_util/cache/utilcachecall.py .The text was updated successfully, but these errors were encountered: