-
-
Notifications
You must be signed in to change notification settings - Fork 3k
[mypyc] Faster min #10265
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
[mypyc] Faster min #10265
Conversation
mypyc/irbuild/specialize.py
Outdated
and expr.arg_kinds == [ARG_POS, ARG_POS]): | ||
x, y = builder.accept(expr.args[0]), builder.accept(expr.args[1]) | ||
comparison = builder.binary_op(x, y, '<', expr.line) | ||
if comparison == Integer(1, bool_rprimitive): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't right. The if statement runs during compilation, but it should instead generate an if statement that is executed when the compiled code is run.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've updated this. However, I posted a question below. Any suggestions please?
def check_min_int() -> None: | ||
x: int = 200 | ||
y: int = 30 | ||
assert min(x, y) == 30 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also test the case where the first argument is the minimum. I think that it's going to fail.
To help debug this, also add an irbuild test case for this. This way you can validate that the code you are generating is what you expect it to be.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I get why you think that way. I've updated the logic a bit to get a better IR. I tested locally and the other case seem to work fine as well. I'll add that test case before pushing next time.
If I recall correctly, the specializer logic would pick the first specializer of a function name. Because there is already a registered |
Okay, I think I'll have to dig in deeper then. I'll keep on updating this thread with all the progress. Thank you :) |
Hello! Okay so I was looking into it and I have a couple of doubts:-
|
For Q1: I suggest you update your local commit so we can figure out why L6 appears. Your guess is correct, it shouldn't be here, it's a generic implementation that is usually slower than direct integer comparison. For Q2: my advice is to make the specializers from a map of <name, function> to <name, list of functions> and update the lookup logic accordingly. But this seems a little bit heavy, not sure how @JukkaL thinks. |
I've pushed my local commits, although they are still WIP
Okay, that makes sense. I can proceed that way if that's okay. |
Agree with this idea. Recent speed-up commits and issues have and will change a large range of code structure. Maybe we can open a new issue to discuss this? |
This is a good idea. My first impression is that having a list of functions is probably fine, but it's worth thinking about it a bit more. |
mypyc/irbuild/specialize.py
Outdated
|
||
@specialize_function('builtins.min') | ||
def faster_min(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> None: | ||
if (len(expr.args) > 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The return value should be Optional[Value]
. Then the call translation functions in irbuild/expression.py
can get the correct result and stop further trying.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was super helpful, thanks for pointing out 😄 I should have been more careful.
lgtm! This PR brings a promising speed-up:
Master branch is about 0.9x. |
|
||
[case testMin] | ||
def check_min_int() -> None: | ||
x: int = 200 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think functions with prefix check
wouldn't trigger a run test? Maybe you should change it to test_*
. Also, please add more run tests about other types, such as strings, floats.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm having some issues with my system right now. I'll try to update the branch by the weekend.
If this is good, then I can the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for contributing! I have several suggesions on testing. The specializer looks good to me and you could support max
in this PR.
btw, we don't recommend force-push since it would break previous reviews.
@@ -20,3 +20,9 @@ def test_abs() -> None: | |||
assert abs(44324.732) == 44324.732 | |||
assert abs(-23.4) == 23.4 | |||
assert abs(-43.44e-4) == 43.44e-4 | |||
|
|||
[case testFloatMin] | |||
def test_float_min() -> None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think merging these test cases into one might be better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
mypyc/test-data/fixtures/ir.py
Outdated
@overload | ||
def min(x: float, y: float) -> float: ... | ||
@overload | ||
def min(x: str, y: str) -> str: ... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use T
(already defined in this file) to make them simple.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added
mypyc/test-data/run-strings.test
Outdated
def test_str_min() -> None: | ||
x: str = 'aaa' | ||
y: str = 'bbb' | ||
assert min(x, y) == 'aaa' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can also test the minimun of user defined class which has __lt__
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Test the inverse here as well.
Please also test heterogeneous operands for Finally, test |
Sorry for the late update. I tried all the tests as @JukkaL suggested, they all failed to compile. I get an error like: Pushing the commit so that everyone can see as well.
Do we need to explicitly perform coercion in the new |
I made changes to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left a few comments (not a full review).
mypyc/test-data/run-strings.test
Outdated
def test_str_min() -> None: | ||
x: str = 'aaa' | ||
y: str = 'bbb' | ||
assert min(x, y) == 'aaa' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Test the inverse here as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good! I checked that min
is now much faster than previously -- it can be over 10x faster than before.
I have just a two minor comments. Feel free to merge once you've addressed them.
Extending this to support max
should be pretty easy and a good follow-up PR :-)
mypyc/irbuild/specialize.py
Outdated
and expr.arg_kinds == [ARG_POS, ARG_POS]): | ||
x, y = builder.accept(expr.args[0]), builder.accept(expr.args[1]) | ||
result = Register(builder.node_type(expr)) | ||
comparison = builder.binary_op(x, y, '<', expr.line) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Based on a quick experiment, it seems that CPython does actually evaluate y < x
instead of x < y
when doing min(x, y)
. Can you double check this and update the implementation accordingly if that's the case? (This is not a big deal, but it's better to remain as close to CPython as possible.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for pointing out! I checked the bltinmodule/min_max()
in CPython and it does eval y < x
when doing min(x, y)
. To be more specific, it evaluates arguments from last to first.
Description Closes mypyc/mypyc#773, follows up to #10265
Speeds up `min(x, y)` using a specializer. Co-authored-by: 97littleleaf11 <[email protected]>
Description Closes mypyc/mypyc#773, follows up to python#10265
Description
I have tried to specialize
min(x, y)
in this PR as per mypyc/mypyc#773Test Plan
I ran the complete test suite locally as well as https://github.com/mypyc/mypyc-benchmarks
However, I'm still unsure as to how am I supposed to report benchmark results. After running the benchmark runner on both master and this branch for 5 time, I got the following results (averaged):
master: 11.7058x faster
current branch: 11.8452x faster