GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
GeezerVison is showing github.com — gh-158592: Reallocate the list's array when deleting by index by x42005e1f · Pull Request #158602 · python/cpython Conversation
|
| Benchmark | before | after |
|---|---|---|
| 0-1 items | 19.3 ns | 50.3 ns: 2.61x slower |
| 1-2 items | 19.3 ns | 27.1 ns: 1.40x slower |
The first case always reallocates the array, while the second case never reallocates the array (see #158602 (comment)). The results appear to be a performance regression, but it is important to understand what this change actually does. Namely, it simply restores the missed reallocation that other methods already perform (del list[slice], list.pop(), list.remove(), etc.)! If you are concerned about this, you should change the list_resize() implementation rather than making the collection non-shrinkable in terms of capacity.
I decided not to add a regression test because it would depend on the exact behavior of the list_resize() function (specifically, when it decides to change the capacity), which would require updating the test whenever its condition changed.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There's not much to change in Do you have benchmarks on the memory usage improvement? If all we're saving is a few bytes while being 160% slower, then I would be -1 on this. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
I do not understand the question. The issue is that this operation never (!) frees up memory. You can fill the list with 1_000 elements (+8 KB), or with 1_000_000 elements (+8 MB), and no |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
|
Let me put it another way: if you think this is not worth changing, then why does the list shrink at all during other operations that remove items? Why do not we just remove the shrinking altogether, and thus speed up all similar methods (for example, |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
A question but:
Who is the "you"? |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Deleting an item from a list is common. Deleting many items at once using
I meant by running our test suite or something like that, or just by running your microbenchmark. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
A potential reader who is able to contribute to the repository. Excuse me if the tone of this sentence seems out of place (actually, I was just trying to point out the logic; though, there are already so many of my stalled issues in python/cpython that perhaps I am wasting my time on this…). I had my doubts about it too; editing it a couple of hours after posting seems wrong to me.
It is not necessary to remove many items at once. Surely there is someone who uses
Again, I do not understand what you want me to measure. In the current implementation, the list capacity does not decrease when deleting by index. Why measure logic that is already clearly demonstrated in the issue using |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Btw, we do bypass the reallocation in list_resize() sometimes: Depending on the allocated value, you might be hitting different paths. I wonder if your empty case is some magical threshold (it wouldbe good to know what happens with lists of various sizes as all your tests are only for lists of size 0 or 1, and I don't know if this happy path is taken or not and which path is actually taken). I also believe that the What's the effect of that on non-free threaded builds (or on FT builds, I don't know which one you used). So the call to
After your PR, is the |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
Hm? They wouldn't need to free things up. If you're frequently deleting and appending, then not resizing on delete is the more performant option. Generally speaking, I'm more for speed efficiency than for memory efficiency (Python users rarely care about saving a little memory, and those who do will be using the |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
The first case:
The second case:
I was wrong about the second case, since it actually reallocates the array (after the PR), but to the exact same capacity (perhaps an oversight in the implementation?). See
I agree, but I am not sure how to do that with pyperf. So I literally did exactly what you asked for in #158592 (comment).
In theory, its performance will be close to that of Oh, yes, I used a non-free-threaded build. If it had been otherwise, I would have mentioned it. But since the issue is not bound to free threading, I assumed it was obvious.
Yes. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
The peak size is not the same as the average size, I should note. From a memory perspective, not freeing up memory is much worse, especially if there are many lists.
I disagree. Lists of arbitrary objects are not what NumPy is about. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
For 3-4 items:
(Before: 19.3, so +2.1 ns) |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
To be clear, you can simply compare Well, it seems a bit off that we are having a discussion here about whether the fix is actually needed, which essentially continues the discussion from the issue. This does not really feel like a review, but… oh well. If we imagine that we are from the Ministry of Silly Walks… (just a joke, in support of how I myself was mistaken about the behavior of |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
We're just being cautious; "2.61x slower" sets off alarm bells for a maintainer. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
|
Okay, what are you requesting from me? Remember, this PR simply restores the behavior that existed prior to 3.13. The Lines 776 to 777 in 58ed60b
And so (in Lines 693 to 703 in 58ed60b
Nothing new is being introduced in this PR. Performance-wise, nothing new is added either; this change is simply a copy-paste from I do not mind if the PR (and the issue) gets closed. I found the problem while analyzing behavior in free threading on weaker memory models (https://discuss.python.org/t/define-a-concurrency-model-aka-memory-model-for-python/108762/34), and noticed that To be honest, I do not think that comparing performance to an implementation that is incorrect (in my opinion) is the right approach. We should compare it to a correct implementation (one that knows how to free memory), that is, to 3.12 or |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
We are not saying the PR won't be merged. We want to be sure we are not regressing from a timing perspective. Memory regressions have been WAY less annoying for users than timing regressions.
To be blunt, we would like you to be more patient. We are not really against the change but we need to properly evaluate it. Since 3.13 the interpreter has changed, as well as the GC, so this change, while simple, may have different implications. We need to evaluate that. We understand clearly the fix, that's not the issue. I don't think you need to relink every portion of the code. I personally think it's ok to make the change as there doesn't seem to be regressions. FTR, I don't think this will really be an issue in practice because:
But I'm ok with changing this. I don't think we need to backport it but I would do so just to prevent annoying conflicts in future backports. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
@corona10, excuse me for pinging you, but could you please explain to the participants in this discussion why you used |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
I don't doubt that it was a mistake, but I think it might have been a happy accident. I ask that you please stop taking this personally and understand our point of view as maintainers:
I think that my initial -1 comment was premature, so I apologize, but I also think we need more time to consider this. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
Co-authored-by: Bénédikt Tran <[email protected]>
|
IIRC, it was for thread safety; in free-threaded builds, other threads can read ob_item, so list_resize()'s realloc/free risked use-after-free for other threads. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
+1 on this. Please read the PEP 779 acceptance message: we care a lot about the single-threaded performance gap between the free-threaded build and the default build. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Also, the free-threaded and default builds already use different reference counting approaches (biased refcount + QSBR), so deallocation timing already differs between them. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
But as I see, now (3.13+)
I do not think it is relevant to refer to that. The path is used in both builds, and it does not depend on free threading in any way. It is simply the standard expected behavior from a structure similar to a dynamic array. Once again: I ran all the tests on a non-free-threaded build. You can reproduce the issue even on Pyodide:
|
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
I am preparing a better approach than this with data analysis. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Please update NEWS.d, this will be the technially correct when considering #158787
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
|
A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated. Once you have made the requested changes, please leave a comment on this pull request containing the phrase |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
Co-authored-by: Bénédikt Tran <[email protected]>
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
lgtm!
I will merge this PR after #158787 get approval.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
Successfully merging this pull request may close these issues.
There was an error while loading. Please reload this page.