Skip to content

perf: recycle 16 KiB buffers via per-thread free-list with posix_memalign(4096) #139

Description

@tjjh89017

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:

  1. 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.
  2. 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.
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions