Skip to content

gh-113141: Tune MI_SEGMENT_SLICE_SHIFT to 14 for using 16 Kib pages - #160360

Open
corona10 wants to merge 3 commits into
python:mainfrom
corona10:MI_SEGMENT_SLICE_SHIFT-expor
Open

corona10 wants to merge 3 commits into
python:mainfrom
corona10:MI_SEGMENT_SLICE_SHIFT-expor

Conversation

@corona10

@corona10 corona10 commented Oct 22, 2026 •

Copy link
Copy Markdown
Member

@DinoV

DinoV commented Oct 22, 2026

Copy link
Copy Markdown
Contributor

@corona10

Copy link
Copy Markdown
Member Author

Based on an analysis of python -c "import re", with help from a script
written with Claude and run under gdb.

The effect of reducing the page size differs by object size.

Scope AS-IS pages AS-IS resident AS-IS live AS-IS util TO-BE pages TO-BE resident TO-BE live TO-BE util
All heaps 149 5480 KiB 4106 KiB 75 % 309 4964 KiB 4102 KiB 83 %
MEM 46 1668 KiB 1146 KiB 69 % 77 1420 KiB 1142 KiB 80 %
OBJECT 45 1604 KiB 1083 KiB 68 % 88 1344 KiB 1083 KiB 81 %
GC 47 2108 KiB 1841 KiB 87 % 133 2088 KiB 1841 KiB 88 %
GC_PRE 11 100 KiB 35 KiB 35 % 11 112 KiB 35 KiB 32 %
<=256 B 68 2540 KiB 2228 KiB 88 % 174 2556 KiB 2228 KiB 87 %
256 B-2 KiB 47 1636 KiB 1346 KiB 82 % 109 1608 KiB 1346 KiB 84 %
2-64 KiB 30 1088 KiB 315 KiB 29 % 22 584 KiB 311 KiB 53 %
>64 KiB 4 216 KiB 216 KiB 100 % 4 216 KiB 216 KiB 100 %

@corona10

Copy link
Copy Markdown
Member Author

cc @nascheme

@corona10
corona10 requested review from DinoV and nascheme October 22, 2026 23:10
@corona10
corona10 requested a review from mpage October 23, 2026 05:12
@corona10

Copy link
Copy Markdown
Member Author

@DinoV @nascheme @mpage
According to the benchmark results, this change reduces memory usage by 3% compared to the baseline, with no performance regression. Although there is no comparison with the default build, the results do confirm a reduction in memory usage.

@corona10
corona10 marked this pull request as ready for review October 23, 2026 05:13
@corona10

Copy link
Copy Markdown
Member Author

cc @daanx

// Do not use guarded allocations. The free-threaded GC does not support their block layout.
# define MI_GUARDED 0
// Use 16 KiB pages instead of the default 64 KiB in 64-bit platforms.
// TODO: 32-bit platforms may need tuning too.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may need to tune a 32-bit system, but I prefer to pass at this time.

@corona10

Copy link
Copy Markdown
Member Author

@nascheme @DinoV
The results from Tools/gcbench from Thomas show that reducing the page size from 64 KiB to 16 KiB increases GC time by 6–8%, since each collection visits four times as many pages. GC-bound pathological cases are 5–8% slower, while steady-state workloads and pyperformance show no change in total runtime.

Peak RSS is generally unchanged on these large-heap tests, although bigcollect/container shows an 11% increase. That case would be worth a separate follow-up if we decide to merge this PR.

This PR aims to reduce the memory overhead relative to the default build from the current 18% to 15%, as measured by pyperformance. I think we should also compare against the default build using the GC benchmarks to make sure both the performance and memory overheads remain within the guardrails outlined in PEP 779. If both criteria are met, I would like to propose merging this PR.

I’ll share the results once I’ve run the GC benchmarks against the default build.

@corona10

corona10 commented Oct 29, 2026 •

Copy link
Copy Markdown
Member Author

Even in GC benchmark this tune does not harmful to the current free-threaded build
(I re-oreganize as pyperf style result)

Benchmarks with tag 'pathology': time

Benchmark default FT 64K FT 16K
gen_frag sparse 583 ms 1.12 sec: 1.92x slower 1.22 sec: 2.10x slower
gen_frag churn 5.07 sec 3.60 sec: 1.41x faster 3.97 sec: 1.28x faster
gen_frag churn acyclic 4.63 sec 2.83 sec: 1.64x faster 2.95 sec: 1.57x faster
bigcollect 104 ms 97 ms: 1.07x faster 103 ms: 1.01x faster
container 446 ms 382 ms: 1.17x faster 399 ms: 1.12x faster
doctree 6.19 sec 4.67 sec: 1.33x faster 4.90 sec: 1.26x faster
migrations 6.62 sec 5.02 sec: 1.32x faster 5.21 sec: 1.27x faster
deque 555 ms 774 ms: 1.40x slower 751 ms: 1.35x slower
bigbuffers 200KB (†) 51 ms 103 ms: 2.00x slower 96 ms: 1.87x slower
bigbuffers 4MB (†) 3.34 sec 1.23 sec: 2.72x faster 417 ms: 8.01x faster
ext_buffers_malloc (*) 549,634 ops/s 337,796 ops/s: 1.63x slower 338,180 ops/s: 1.63x slower
ext_buffers_pymem 558,001 ops/s 325,207 ops/s: 1.72x slower 301,579 ops/s: 1.85x slower
hidden_refs 1,589,119 ops/s 470,105 ops/s: 3.38x slower 499,478 ops/s: 3.18x slower
c_globals 291,985 ops/s 227,908 ops/s: 1.28x slower 223,261 ops/s: 1.31x slower
py_globals 283,106 ops/s 241,875 ops/s: 1.17x slower 235,612 ops/s: 1.20x slower
event_widgets runaway (*) 33,171 ops/s 45,872 ops/s: 1.38x faster 41,341 ops/s: 1.25x faster
Geometric mean (ref) 1.13x slower 1.17x slower
Geometric mean incl. (†) (ref) 1.09x slower 1.05x slower

Benchmarks with tag 'pathology': peak memory

Benchmark default FT 64K FT 16K
gen_frag sparse 49 MB 54 MB: 1.10x 54 MB: 1.11x
gen_frag churn 90 MB 88 MB: 0.98x 88 MB: 0.98x
gen_frag churn acyclic 88 MB 88 MB: 1.00x 88 MB: 1.00x
bigcollect 260 MB 275 MB: 1.06x 305 MB: 1.17x
container 245 MB 273 MB: 1.11x 304 MB: 1.24x
doctree 1,165 MB 1,208 MB: 1.04x 1,225 MB: 1.05x
migrations 277 MB 298 MB: 1.08x 301 MB: 1.09x
deque 246 MB 105 MB: 0.43x 91 MB: 0.37x
bigbuffers 200KB 204 MB 418 MB: 2.05x 420 MB: 2.06x
bigbuffers 4MB 1,907 MB 2,017 MB: 1.06x 2,031 MB: 1.07x
ext_buffers_malloc (*) 188 MB 392 MB: 2.08x 508 MB: 2.70x
ext_buffers_pymem 186 MB 439 MB: 2.36x 441 MB: 2.37x
hidden_refs 306 MB 420 MB: 1.37x 424 MB: 1.39x
c_globals 124 MB 170 MB: 1.37x 173 MB: 1.40x
py_globals 124 MB 171 MB: 1.38x 169 MB: 1.36x
event_widgets runaway (*) 188 MB 2,255 MB: 11.97x 2,234 MB: 11.85x
Geometric mean (ref) 1.39x 1.43x

Benchmarks with tag 'workloads': time

Benchmark default FT 64K FT 16K
web_service 52,525 ops/s 48,112 ops/s: 1.09x slower 51,578 ops/s: 1.02x slower
orm_session mixed 2,181 ops/s 1,759 ops/s: 1.24x slower 1,785 ops/s: 1.22x slower
async_server 8,841 ops/s 7,130 ops/s: 1.24x slower 7,434 ops/s: 1.19x slower
json_churn 64,798 ops/s 57,431 ops/s: 1.13x slower 57,336 ops/s: 1.13x slower
Geometric mean (ref) 1.17x slower 1.14x slower

Benchmarks with tag 'workloads': peak memory

Benchmark default FT 64K FT 16K
web_service 376 MB 494 MB: 1.31x 502 MB: 1.34x
orm_session mixed 85 MB 124 MB: 1.46x 126 MB: 1.49x
async_server 43 MB 57 MB: 1.34x 55 MB: 1.30x
json_churn 83 MB 98 MB: 1.17x 98 MB: 1.18x
Geometric mean (ref) 1.32x 1.32x

Benchmarks with tag 'threads': time

Benchmark default FT 64K FT 16K
web_service 4t 61,093 ops/s 148,439 ops/s: 2.43x faster 150,626 ops/s: 2.47x faster
web_service 8t 64,679 ops/s 140,723 ops/s: 2.18x faster 147,049 ops/s: 2.27x faster
orm_session mixed 4t 2,085 ops/s 4,975 ops/s: 2.39x faster 5,266 ops/s: 2.53x faster
Geometric mean (ref) 2.33x faster 2.42x faster

Benchmarks with tag 'threads': peak memory

Benchmark default FT 64K FT 16K
web_service 4t 399 MB 486 MB: 1.22x 492 MB: 1.23x
web_service 8t 419 MB 492 MB: 1.17x 498 MB: 1.19x
orm_session mixed 4t 107 MB 152 MB: 1.42x 147 MB: 1.37x
Geometric mean (ref) 1.27x 1.26x

Overall

  • Time geometric mean (21 benchmarks, excluding (†)): FT 64K 1.01x faster, FT 16K 1.01x slower
  • Time geometric mean incl. (†) (23): FT 64K 1.02x faster, FT 16K 1.06x faster
  • (†) bigbuffers: README says the runs are too short to time; shown but excluded from the main geomean. (*) event_widgets runaway, ext_buffers_malloc: large run-to-run variance.
  • Memory usage (23 benchmarks): FT 64K 1.36x, FT 16K 1.38x
  • Memory usage without (*) benchmarks (21): FT 64K 1.20x, FT 16K 1.21x
  • FT 16K vs FT 64K memory: 1.016x (without (*): 1.006x)

@nascheme

nascheme commented Oct 30, 2026 •

Copy link
Copy Markdown
Member

This seems to be a classic time-vs-space tradeoff. Generally, I would lean towards sticking with upstream 64k since it doesn't make a large difference. However, since Donghee did extensive testing and it does seem a small win with no major regressions, this seems safe to change. Another point: if we can port some of the GC optimizations from the default build, doing fewer heap traversals by combining passes, this would likely be a bigger win.

@DinoV DinoV left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants