List overallocation strategy #82554
Copy link
Copy link
Closed
Labels
3.9 (EOL)end of lifeend of lifeinterpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)performancePerformance or resource usagePerformance or resource usage
Description
| BPO | 38373 |
|---|---|
| Nosy | @tim-one, @rhettinger, @methane, @serhiy-storchaka, @brandtbucher |
| PRs |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
assignee = None
closed_at = <Date 2020-03-17.21:50:33.814>
created_at = <Date 2019-10-04.22:43:43.056>
labels = ['interpreter-core', '3.9', 'performance']
title = 'List overallocation strategy'
updated_at = <Date 2020-03-17.21:50:33.813>
user = 'https://github.com/serhiy-storchaka'bugs.python.org fields:
activity = <Date 2020-03-17.21:50:33.813>
actor = 'serhiy.storchaka'
assignee = 'none'
closed = True
closed_date = <Date 2020-03-17.21:50:33.814>
closer = 'serhiy.storchaka'
components = ['Interpreter Core']
creation = <Date 2019-10-04.22:43:43.056>
creator = 'serhiy.storchaka'
dependencies = []
files = []
hgrepos = []
issue_num = 38373
keywords = ['patch']
message_count = 7.0
messages = ['353976', '353978', '353979', '353986', '353991', '353993', '364483']
nosy_count = 5.0
nosy_names = ['tim.peters', 'rhettinger', 'methane', 'serhiy.storchaka', 'brandtbucher']
pr_nums = ['18952']
priority = 'normal'
resolution = 'fixed'
stage = 'resolved'
status = 'closed'
superseder = None
type = 'resource usage'
url = 'https://bugs.python.org/issue38373'
versions = ['Python 3.9']Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
3.9 (EOL)end of lifeend of lifeinterpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)performancePerformance or resource usagePerformance or resource usage
Activity
serhiy-storchaka commentedon Oct 4, 2019
Currently list uses the following formula for allocating an array for items (if size != 0):
If add items by 1 the growth pattern is: 0, 4, 8, 16, 25, 35, 46, 58, 72, 88, ...
I think this strategy can be improved.
So it may be better to allocate a multiple of two or four items. Few first sizes are multiple of four, so this is good multiplier. I suggest to use the following expression:
It adds bits AND, but removes conditional value.
List display allocates a space for exactly 3 items. But a list created from a tuple allocates a space for 6 items.
Other example. Let extend a list by 10 items at a time.
size allocated
10 17
20 28
30 39
40 51
50 51
60 73
70 73
80 96
90 96
100 118
We need to reallocate an array after first four steps. 17 is too small for 20, 28 is too small for 30, 39 is too small for 40. So overallocating does not help, it just spends a space.
My idea, that if we adds several items at a time and need to reallocate an array, we check if the overallocated size is enough for adding the same amount of items next time. If it is not enough, we do not overallocate.
The final algorithm is:
It will save a space if extend a list by many items few times.
rhettinger commentedon Oct 4, 2019
We should definitely revisit the over-allocation strategy. I last worked on the existing strategy back in 2004. Since then, the weighting of the speed/space trade-off considerations have changed.
We need to keep the amortized O(1) append() behavior, but possibly we would benefit from more over-allocation and fewer resizes. Also, we should look at the interaction with the small object allocator to see if its design is causing realloc() to frequently have to move data rather than extending in place.
serhiy-storchaka commentedon Oct 4, 2019
Here is some data. step is the number of items added at a time, the first row is the size, the second row is the currently allocated size, the third row is the proposed allocated size.
tim-one commentedon Oct 5, 2019
WRT pymalloc, it will always copy on growing resize in this context. A pymalloc pool is dedicated to blocks of the same size class, so if the size class increases (they're 16 bytes apart now), the data must be copied to a different pool (dedicated to blocks of the larger size class).
rhettinger commentedon Oct 5, 2019
That sounds like an excellent reason NOT to use pymalloc ;-)
tim-one commentedon Oct 5, 2019
Don't know. Define "the problem" ;-) As soon as the allocation is over 512 bytes (64 pointers), it's punted to the system malloc family. Before then, do a relative handful of relatively small memcpy's really matter?
pymalloc is faster than system mallocs, and probably uses space more efficiently. There are no pure wins here :-)
serhiy-storchaka commentedon Mar 17, 2020
New changeset 2fe815e by Serhiy Storchaka in branch 'master':
bpo-38373: Change list overallocating strategy. (GH-18952)
2fe815e