-
-
Notifications
You must be signed in to change notification settings - Fork 3k
[mypyc] Build lists using a primitive op #10807
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
Conversation
Well, #9378 |
Oops, it seems like a revert |
The performance impact seems a bit too big to make this worthwhile as is. However, maybe we can come up with something reasonable with some tweaks. First, in most cases the constructed list has 0 or 1 items. In this case I'd expect the performance impact to be bigger than for larger lists. It would be good to have microbenchmark results for these cases as well (esp. when the item is a literal, such as a string). Since 0 and 1 items are very common, I think that we can't really afford to lose any performance there. One option would be to fall back to the original behavior for small lists -- at least 0 and 1 items, possibly up to 3-5 items or so. The code size impact becomes significant for larger lists, but it's not such a problem for small lists. Second, this will sometimes result in extra incref/decref that we don't really need. I think that we can avoid the incref in the function by stealing the items (see |
Microbenchmark results
Empty list:
One item:
5 items:
10 items:
|
I probably would set 5 as the threshold. Larger lists should use |
I feel that given the performance numbers you've collected in this PR, 10 would be the ideal threshold. Also I feel 10 is the upper bound people tend to write inline list construction code so making larger than 10 items a single function call is totally acceptable. Also, there's another more pragmatic way to do this is to collect stats from open-source code and analyze them to see what's the threshold value to cover, say, 95% cases and make the decision based on this observation. |
@@ -927,23 +927,8 @@ def new_list_op_with_length(self, length: Value, line: int) -> Value: | |||
return self.call_c(new_list_op, [length], line) | |||
|
|||
def new_list_op(self, values: List[Value], line: int) -> Value: | |||
length = Integer(len(values), c_pyssize_t_rprimitive, 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.
Is make_list
a good name? since we have another new_list_op_with_length
for creating empty list.
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.
Ummm, I saw we have different namings now like make_dict
and new_set_op
, I think we should adopt one and stick to it, like make_xxx
or new_xxx_op
but not both.
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! I have few comments here.
mypyc/test-data/irbuild-lists.test
Outdated
from typing import List | ||
def f(a: List[int]) -> None: | ||
b = a * 2 | ||
b = 3 * [4] |
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.
Why are you deleting this test? It looks like i served some unrelated purpose (check list multiplication)
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.
oops, it seems that I mistakenly overwritten it.
mypyc/test-data/run-lists.test
Outdated
l.pop() | ||
l.pop() | ||
assert l == [1, 2, 10, 3] | ||
l.pop() | ||
assert l == [1, 2, 10, 3, 4, 5, 7] |
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.
Please keep test for both short and long lists, and add a comment explaining this, that mentions the LIST_BUILDING_EXPANSION_THRESHOLD
,
r8 = 'i' | ||
r9 = 'j' | ||
r10 = CPyList_Build(10, r0, r1, r2, r3, r4, r5, r6, r7, r8, r9) | ||
x = r10 |
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.
Do we have IR tests for both short and long list literals?
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.
We have dozens of list building cases, whose length is less than 10, in other irbuilding tests. So I think we only need one more test for building a 10 items list.
mypyc/irbuild/ll_builder.py
Outdated
length: List[Value] = [Integer(len(values), c_pyssize_t_rprimitive, line)] | ||
if len(values) >= LIST_BUILDING_EXPANSION_THRESHOLD: | ||
return self.call_c(list_build_op, length + values, line) | ||
else: |
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 else
is not needed here, since if
body is a plain return. This will make the diff simpler.
mypyc/irbuild/ll_builder.py
Outdated
if len(values) >= LIST_BUILDING_EXPANSION_THRESHOLD: | ||
return self.call_c(list_build_op, length + values, line) | ||
else: | ||
result_list = self.call_c(new_list_op, length, 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.
Add a comment to explain that this tricky code is here to speed-up creation of short list literals.
Description
Closes mypyc/mypyc#264
This PR adds a primitive op and a C helper function for building lists.
Test Plan
This change helps reduce the generated code size, however adds some overhead in calling.
Microbenchmark tested on master branch:
On this PR: