Shift-to-Middle Array: A Faster Alternative to Std:Deque?
Overview & Relation to Existing Structures
- Many commenters recognize this as a known pattern: “array deque” / double-ended vector / inside‑out gap buffer variant.
- Similar ideas exist in Boost’s
devector, various Rust crates, Apple’s CFArray/NSMutableArray, JavaScriptCore’s array storage, etc. - Distinctive property: fully contiguous storage while supporting push/pop at both ends; this is the main advantage over ring buffers /
VecDeque(which are circular and discontinuous).
Functionality & Limitations
- Current implementation does not support efficient random deletion.
- Tombstones are suggested, but that breaks the “contiguous, sliceable” property.
- Thread debates whether efficient random deletes and contiguous hole‑free storage are fundamentally at odds; maps/dense structures are mentioned but don’t provide true sliceable arrays.
- Some argue that without stable memory addresses (like
std::dequeguarantees for end operations), it’s closer to a specializedstd::vectorthan a real deque replacement.
Complexity, Pathologies & Performance
- Claimed amortized O(1) end operations are questioned:
- Sliding-window pattern (push_front, pop_back at steady size) appears to cause repeated shifts or same‑size “resizes”, leading to excessive copying or unbounded capacity growth.
- Several comments describe this behavior as almost a memory leak for small queues.
- For classic FIFO queues, a plain ring buffer requires no copying and is expected to be faster; this structure trades extra copying for contiguity.
- Benchmarks:
- Links in the README are partially placeholders; a PDF in the repo shows small, workload‑dependent differences vs
std::deque/std::queue. - Differences are generally interpreted as constant‑factor, not asymptotic, and sometimes only a few percent.
- Suggestions: include
std::dequeas the explicit baseline, add p99 latency, medians, stddev, and better warmup (especially for the Java version).
- Links in the README are partially placeholders; a PDF in the repo shows small, workload‑dependent differences vs
Implementation Quality (C++ and Benchmarks)
- Multiple concrete issues noted in the C++ code:
- Use of
intinstead ofsize_t, incorrectfront(), mixingmalloc/delete[], missing rule‑of‑five, commented‑out resizing fixes, poorstd::bad_allochandling, unnecessaryinlinehints.
- Use of
ExpandingRingBufferbaseline is also criticized for slow modulo indexing and non‑power‑of‑two optimization.- General advice: lean on tooling (sanitizers, warnings, microbenchmark frameworks) and possibly LLMs for finding obvious API and UB bugs.
Alternative Designs & Growth Strategies
- Alternative “double-ended vector” strategy proposed: grow only the side that needs space, rebalance by shifting if possible, and only allocate larger buffers when necessary, with bounded 3N memory overhead.
- Debate around growth factor: 2 vs ~1.5 (golden-ratio‑like); real allocators, power‑of‑two sizes, and cache effects can reverse theoretical preferences.
- “Magic ring buffers” using
mmapdouble‑mapping are discussed as a way to get contiguous views without copying, but with portability, TLB, aliasing, andforkcomplications.
Adoption & Use Cases
- Many find the idea interesting and educational, especially for understanding deque/ring‑buffer tradeoffs.
- Skepticism remains about practical advantage over well‑implemented
std::deque/VecDeque, given:- Small measured gains,
- Implementation bugs, and
- Pathological behavior for common queue patterns.