Title: Issue · GitHub
Open Graph Title: Issue · python/cpython
X Title: Issue · python/cpython
Description: The Python programming language. Contribute to python/cpython development by creating an account on GitHub.
Open Graph Description: The Python programming language. Contribute to python/cpython development by creating an account on GitHub.
X Description: The Python programming language. Contribute to python/cpython development by creating an account on GitHub.
Opengraph URL: https://github.com/python/cpython
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"Add huge pages support for pymalloc","articleBody":"pymalloc allocates small objects from contiguous regions called arenas. On 64-bit platforms each arena is 1 MiB, obtained via `mmap(MAP_PRIVATE|MAP_ANONYMOUS)` and backed by 256 standard 4 KiB pages. Each page needs its own TLB entry, and the x86_64 dTLB only holds 64-128 entries for 4K pages, so a single arena already overflows TLB capacity, and any non-trivial Python program touches many arenas.\n\nMost modern operating systems support \"huge pages\": memory pages much larger than the default 4 KiB. On x86_64 Linux the standard huge page size is 2 MiB. A single 2 MiB huge page is covered by one TLB entry instead of 512 entries for the equivalent range of 4 KiB pages. This dramatically reduces TLB pressure for workloads that touch large contiguous allocations. On Linux, explicit huge pages are allocated via `mmap` with the `MAP_HUGETLB` flag (available since kernel 2.6.32) from a pre-reserved pool configured through `/proc/sys/vm/nr_hugepages`. On Windows, the equivalent is `VirtualAlloc` with `MEM_LARGE_PAGES`.\n\nI'd like to propose adding a `./configure --with-pymalloc-hugepages` option that increases `ARENA_BITS` from 20 to 21 (1 MiB -\u003e 2 MiB) and makes `_PyMem_ArenaAlloc()` try `mmap(MAP_HUGETLB)` first, falling back to regular `mmap` if the huge page pool is exhausted. On Windows the equivalent would be `VirtualAlloc(MEM_LARGE_PAGES)` with fallback. `_PyMem_ArenaFree()` needs no changes since `munmap` handles huge pages identically. All derived constants (`ARENA_SIZE`, `MAX_POOLS_IN_ARENA`, radix tree bit widths, `nfp2lasta` sizing) adjust automatically from `ARENA_BITS`. \n\nThe flag is opt-in and off by default. `MAP_HUGETLB` requires the kernel to have huge pages pre-allocated; without them the fallback path produces identical behavior to a non-hugepages build. On Linux, huge pages are managed through `/proc/sys/vm/nr_hugepages`. To allocate 128 huge pages (256 MiB on x86_64 where the default huge page size is 2 MiB):\n\n```bash\n# Allocate (requires root)\necho 128 | sudo tee /proc/sys/vm/nr_hugepages\n\n# Verify\ngrep HugePages /proc/meminfo\n# HugePages_Total: 128\n# HugePages_Free: 128\n\n# Make persistent across reboots by adding to /etc/sysctl.conf:\n# vm.nr_hugepages = 128\n```\n\nEach arena consumes one huge page. If the pool runs out, obmalloc falls back to regular 4K pages transparently.\n\nI benchmarked on an i9-14900KS, Linux 6.18.3, GCC 15.2.1 on main with `nr_hugepages=128`. Measured with `perf stat -r 100` using `cpu_core` counters. GC disabled during benchmarks.\n\nWall-clock results:\n\n| Benchmark | Default | Hugepages | Change |\n|---|---|---|---|\n| list_of_tuples (1M 3-tuples) | 0.172s | 0.121s | **-29.5%** |\n| fragmentation (500K alloc/free/realloc) | 0.162s | 0.119s | **-26.5%** |\n| mixed_sizes (500K, 12 size classes) | 0.141s | 0.106s | **-25.1%** |\n| bulk_small_alloc (1M bytearrays) | 0.205s | 0.160s | **-22.1%** |\n| class_instances (500K \\_\\_slots\\_\\_) | 0.120s | 0.096s | **-20.0%** |\n| arena_pressure (10x200K objects) | 0.509s | 0.448s | **-12.1%** |\n| random_walk (1M, shuffled access) | 0.822s | 0.759s | **-7.6%** |\n\ndTLB miss reductions:\n\n| Benchmark | dTLB Load Miss | dTLB Store Miss | Page Faults |\n|---|---|---|---|\n| fragmentation | -95.9% | -94.7% | -94.5% |\n| random_walk | -93.1% | -98.9% | -91.6% |\n| bulk_small_alloc | -91.4% | -94.5% | -93.5% |\n| list_of_tuples | -88.0% | -93.7% | -94.1% |\n| class_instances | -84.3% | -91.8% | -92.1% |\n| mixed_sizes | -80.8% | -76.5% | -78.2% |\n\nThe perf command used per benchmark:\n\n```bash\nEVENTS=\"dTLB-loads,dTLB-load-misses,dTLB-stores,dTLB-store-misses,iTLB-load-misses,cache-misses,cache-references,instructions,cycles,page-faults\"\nperf stat -r 10 -e \"$EVENTS\" ./python bench_obmalloc.py fragmentation\n```\n\n\u003cdetails\u003e\n\u003csummary\u003ebench_obmalloc.py\u003c/summary\u003e\n\n```python\nimport sys, gc\n\ndef bench_small_object_churn():\n objs = []\n for _ in range(200_000): objs.append(bytearray(64))\n for _ in range(200_000): objs.append(bytearray(64)); objs.pop(0)\n\ndef bench_bulk_small_alloc():\n objs = [bytearray(48) for _ in range(1_000_000)]\n for o in objs: o[0] = 1\n\ndef bench_dict_churn():\n for _ in range(500_000): d = {\"a\": 1, \"b\": 2, \"c\": 3, \"d\": 4}; del d\n\ndef bench_mixed_sizes():\n sizes = [8, 16, 24, 32, 48, 64, 96, 128, 192, 256, 384, 512]\n objs = [bytearray(sizes[i % 12]) for i in range(500_000)]\n\ndef bench_fragmentation():\n objs = [bytearray(128) for _ in range(500_000)]\n for i in range(0, len(objs), 2): objs[i] = None\n for i in range(0, len(objs), 2): objs[i] = bytearray(128)\n\ndef bench_list_of_tuples():\n objs = [(i, i+1, i+2) for i in range(1_000_000)]\n\ndef bench_class_instances():\n class Pt:\n __slots__ = ('x', 'y', 'z')\n def __init__(s, x, y, z): s.x = x; s.y = y; s.z = z\n objs = [Pt(i, i+1, i+2) for i in range(500_000)]\n\ndef bench_arena_pressure():\n layers = [[bytearray(256) for _ in range(200_000)] for _ in range(10)]\n\ndef bench_random_walk():\n import random; random.seed(42)\n objs = [bytearray(64) for _ in range(1_000_000)]\n idx = list(range(len(objs))); random.shuffle(idx)\n for i in idx: objs[i][0] = i \u0026 0xff\n\nBENCHMARKS = dict(small_object_churn=bench_small_object_churn,\n bulk_small_alloc=bench_bulk_small_alloc, dict_churn=bench_dict_churn,\n mixed_sizes=bench_mixed_sizes, fragmentation=bench_fragmentation,\n list_of_tuples=bench_list_of_tuples, class_instances=bench_class_instances,\n arena_pressure=bench_arena_pressure, random_walk=bench_random_walk)\n\nif __name__ == \"__main__\":\n gc.collect(); gc.disable(); BENCHMARKS[sys.argv[1]](); gc.enable()\n```\n\n\u003c/details\u003e\n\nFull reproduction:\n\n```bash\n./configure \u0026\u0026 make -j$(nproc) \u0026\u0026 cp python python_default\n./configure --with-pymalloc-hugepages \u0026\u0026 make -j$(nproc) \u0026\u0026 cp python python_hugepages\necho 128 | sudo tee /proc/sys/vm/nr_hugepages\n\nEVENTS=\"dTLB-loads,dTLB-load-misses,dTLB-stores,dTLB-store-misses,iTLB-load-misses,cache-misses,cache-references,instructions,cycles,page-faults\"\nfor b in bulk_small_alloc mixed_sizes fragmentation list_of_tuples class_instances arena_pressure random_walk; do\n echo \"=== $b ===\"\n perf stat -r 10 -e \"$EVENTS\" ./python_default bench_obmalloc.py \"$b\"\n perf stat -r 10 -e \"$EVENTS\" ./python_hugepages bench_obmalloc.py \"$b\"\ndone\n```\n\n\u003c!-- gh-linked-prs --\u003e\n### Linked PRs\n* gh-144320\n* gh-144331\n* gh-144353\n* gh-144928\n* gh-147963\n\u003c!-- /gh-linked-prs --\u003e\n","author":{"url":"https://github.com/pablogsal","@type":"Person","name":"pablogsal"},"datePublished":"2026-01-29T01:40:29.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":7},"url":"https://github.com/144319/cpython/issues/144319"}
| route-pattern | /:user_id/:repository/issues/:id(.:format) |
| route-controller | issues |
| route-action | show |
| fetch-nonce | v2:c22f74d3-a109-5d45-f9c5-2ba52f0e5be7 |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | DBF8:3061A4:6D66D8:95B2BD:6A51501C |
| html-safe-nonce | d36b86fdd6c65b5796fe0d6afecf6a2f2b4d5e818b846af9b1d91f865ad2567a |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJEQkY4OjMwNjFBNDo2RDY2RDg6OTVCMkJEOjZBNTE1MDFDIiwidmlzaXRvcl9pZCI6IjY1ODU3NDMwMjQ4NTIxMjc3NzMiLCJyZWdpb25fZWRnZSI6ImlhZCIsInJlZ2lvbl9yZW5kZXIiOiJpYWQifQ== |
| visitor-hmac | 0e8df131cd0ff2cf4e38aeb69597ee0126e56b18606fcaabc4fa7a7145bfa555 |
| hovercard-subject-tag | repository:81598961 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/python/cpython/issues/144319 |
| twitter:image | https://opengraph.githubassets.com/cd107b35a017fb0db1cda56aafbd7fbcf9eb95181788ee9bcb73d23f84d5e94c/python/cpython |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/cd107b35a017fb0db1cda56aafbd7fbcf9eb95181788ee9bcb73d23f84d5e94c/python/cpython |
| og:image:alt | The Python programming language. Contribute to python/cpython development by creating an account on GitHub. |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| hostname | github.com |
| expected-hostname | github.com |
| None | 6f17634ed2af1d116850bcea6c63a70a4d72970f33ade1dd3eef3764b7e16994 |
| turbo-cache-control | no-cache |
| go-import | github.com/python/cpython git https://github.com/python/cpython.git |
| octolytics-dimension-user_id | 1525981 |
| octolytics-dimension-user_login | python |
| octolytics-dimension-repository_id | 81598961 |
| octolytics-dimension-repository_nwo | python/cpython |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 81598961 |
| octolytics-dimension-repository_network_root_nwo | python/cpython |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 511bff6ea06e144ed085ff736aea4c6404987b00 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width