Quick follow-up to #7764, where adding a cache in front of our raw-disk storage made things slower (350-400 MB/s vs 450-600 without, network thoughtput).
(EZIO writes to a raw partition, not a filesystem. Torrent "files" are hex disk offsets, blocks in a piece are physically contiguous. Target is 10G+ LAN + NVMe.)
Why the first cache was slower: it was a shared map + mutex (plus a store_buffer for read-after-write ordering). With aio_threads disk threads all hitting one lock per block, it was a serialization point. The lock ate all of them.
The fix was partitioning instead of locking: Route every block of a piece to one worker thread: hash (storage, piece) (deliberately not the offset) to pick a partition, one thread owns one partition.
- No mutex anywhere: a partition is only touched by its owner, so map/LRU/stats need no locking.
- Read-after-write ordering without
store_buffer: same piece always lands on the same thread, so async_read naturally queues behind async_write. Deleted the whole store_buffer.
The reason offset can't go into the hash: async_hash reads a piece's blocks straight from the cache, so scattering them across partitions reintroduces the race you just removed. Keeping a whole piece thread-local is what keeps it lock-free.
Result on 1-on-1 (--cache-size 4096 --aio-threads 16, 60 GiB, NVMe both ends): download rate sustains ~840 MB/s, peaks ~870, 98-100% hit rate. Flush-to-disk is slower (~540 MiB/s) and that's the point, the cache lets the network run ahead of the disk.
Code: https://github.com/tjjh89017/ezio (raw_disk_io.cpp + the lock-free cache).
Thanks @arvidn again always.
Quick follow-up to #7764, where adding a cache in front of our raw-disk storage made things slower (350-400 MB/s vs 450-600 without, network thoughtput).
(EZIO writes to a raw partition, not a filesystem. Torrent "files" are hex disk offsets, blocks in a piece are physically contiguous. Target is 10G+ LAN + NVMe.)
Why the first cache was slower: it was a shared map + mutex (plus a
store_bufferfor read-after-write ordering). Withaio_threadsdisk threads all hitting one lock per block, it was a serialization point. The lock ate all of them.The fix was partitioning instead of locking: Route every block of a piece to one worker thread: hash
(storage, piece)(deliberately not the offset) to pick a partition, one thread owns one partition.store_buffer: same piece always lands on the same thread, soasync_readnaturally queues behindasync_write. Deleted the whole store_buffer.The reason offset can't go into the hash:
async_hashreads a piece's blocks straight from the cache, so scattering them across partitions reintroduces the race you just removed. Keeping a whole piece thread-local is what keeps it lock-free.Result on 1-on-1 (
--cache-size 4096 --aio-threads 16, 60 GiB, NVMe both ends): download rate sustains ~840 MB/s, peaks ~870, 98-100% hit rate. Flush-to-disk is slower (~540 MiB/s) and that's the point, the cache lets the network run ahead of the disk.Code: https://github.com/tjjh89017/ezio (
raw_disk_io.cpp+ the lock-free cache).Thanks @arvidn again always.