Problem
Both buffer sources allocate every 16 KiB block with plain malloc/free, with no recycling:
buffer_pool::allocate_buffer_impl -> malloc(DEFAULT_BLOCK_SIZE) (buffer_pool.cpp:40), free() (:78). The "pool" is only an m_size counter + watermark; it does not recycle buffers.
cache_partition::insert -> malloc(DEFAULT_BLOCK_SIZE) (unified_cache.cpp:65), freed on evict.
(For reference, libtorrent RC_2_1's disk_buffer_pool is also plain malloc/free — no recycling there to borrow from.)
16 KiB is above glibc's tcache ceiling (~1032 B), so every allocation takes the largebin path: arena lock + best-fit search + split/coalesce. At line rate this runs tens of thousands of times/s across 16 worker threads.
Proposed fix
Recycle fixed-size 16 KiB buffers via a per-owner free-list instead of returning them to libc:
- Cache buffers: one free-list per partition. EZIO's 1:1 thread:partition model makes this lock-free by construction (single-threaded access already guaranteed).
- buffer_pool buffers: one free-list on the network thread (buffer_pool's documented single-thread owner).
Allocate the backing blocks with posix_memalign(&p, 4096, 16384):
- We are already block-aligned (16 KiB blocks, piece-aligned offsets), so 4 KiB alignment is natural.
- This makes the buffers O_DIRECT-ready for free (alignment is a prerequisite — see the O_DIRECT investigation), so this issue unblocks that path.
posix_memalign is part of POSIX and is always available on Linux/glibc. If a platform ever lacks it, fall back to C11 aligned_alloc (size already a multiple of alignment) or memalign. No fallback to plain malloc for the O_DIRECT path (alignment is required there).
Free-list ops are O(1) push/pop with no size-class search, no coalescing, no arena lock.
Expected performance gain (to be measured)
Benefits, in order of confidence:
- Removes largebin malloc/free lock + best-fit search on the per-block hot path -> lower worker-thread CPU and less arena-lock contention across 16 threads. The contention reduction is the main expected win under high concurrency (many leechers / many connections), and shows up as lower tail latency more than higher peak MB/s.
- Keeps pages resident and cache/TLB-warm — avoids minor page faults from libc returning freed memory to the OS (malloc_trim / MADV_DONTNEED) and re-faulting on next touch.
- Unblocks O_DIRECT by providing aligned buffers (strategic, not a direct perf number).
Rough magnitude: low single-digit % CPU on the disk workers, larger relative effect under high thread/connection counts where arena contention dominates. This is not expected to move bandwidth-bound peak throughput much; the value is CPU headroom + contention + enabling O_DIRECT.
Must be measured per the distrib_test methodology (1 seeder + multiple leechers, blkdiscard between runs) before quoting a figure — and specifically under a high connection count where allocator contention is most visible.
Notes
- Cap the free-list size (e.g. to the existing watermark) so idle memory is bounded; trim back to libc above the cap.
- Keep the existing watermark/backpressure semantics in buffer_pool.
Context
NVMe disk-I/O acceleration review. Related: #138 (offset precompute), and the O_DIRECT candidate which depends on the aligned allocation introduced here.
Problem
Both buffer sources allocate every 16 KiB block with plain
malloc/free, with no recycling:buffer_pool::allocate_buffer_impl->malloc(DEFAULT_BLOCK_SIZE)(buffer_pool.cpp:40),free()(:78). The "pool" is only anm_sizecounter + watermark; it does not recycle buffers.cache_partition::insert->malloc(DEFAULT_BLOCK_SIZE)(unified_cache.cpp:65), freed on evict.(For reference, libtorrent RC_2_1's
disk_buffer_poolis also plainmalloc/free— no recycling there to borrow from.)16 KiB is above glibc's tcache ceiling (~1032 B), so every allocation takes the largebin path: arena lock + best-fit search + split/coalesce. At line rate this runs tens of thousands of times/s across 16 worker threads.
Proposed fix
Recycle fixed-size 16 KiB buffers via a per-owner free-list instead of returning them to libc:
Allocate the backing blocks with
posix_memalign(&p, 4096, 16384):posix_memalignis part of POSIX and is always available on Linux/glibc. If a platform ever lacks it, fall back to C11aligned_alloc(size already a multiple of alignment) ormemalign. No fallback to plainmallocfor the O_DIRECT path (alignment is required there).Free-list ops are O(1) push/pop with no size-class search, no coalescing, no arena lock.
Expected performance gain (to be measured)
Benefits, in order of confidence:
Rough magnitude: low single-digit % CPU on the disk workers, larger relative effect under high thread/connection counts where arena contention dominates. This is not expected to move bandwidth-bound peak throughput much; the value is CPU headroom + contention + enabling O_DIRECT.
Must be measured per the distrib_test methodology (1 seeder + multiple leechers,
blkdiscardbetween runs) before quoting a figure — and specifically under a high connection count where allocator contention is most visible.Notes
Context
NVMe disk-I/O acceleration review. Related: #138 (offset precompute), and the O_DIRECT candidate which depends on the aligned allocation introduced here.