Skip to content

optimize linked_list - #66

Merged
awolverp merged 6 commits into
awolverp:mainfrom
chirizxc:linked_list
Aug 5, 2026
Merged

optimize linked_list#66
awolverp merged 6 commits into
awolverp:mainfrom
chirizxc:linked_list

Conversation

@chirizxc

@chirizxcchirizxc commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Compiler Explorer GodBolt

Compiler ExplorerGodBolt Difff

How do I view the diff after clicking the link? 🤔изображениеизображение

TODO

Review // SAFETY and // # Safety again, most of them were generated by AI, and we need to double-check that they are correct

Benchmark

#![allow(dead_code)]use divan::Bencher;mod old {use std::marker::PhantomData;use std::mem;use std::ptr::NonNull;pubstructNode<T>{next:Option<NonNull<Node<T>>>,prev:Option<NonNull<Node<T>>>,element:T,}impl<T>Node<T>{fnnew(element:T) -> Self{Node{next:None,prev:None,
element,}}#[allow(clippy::boxed_local)]fninto_element(self:Box<Self>) -> T{self.element}pubfnelement(&self) -> &T{&self.element}}pubstructLinkedList<T>{head:Option<NonNull<Node<T>>>,tail:Option<NonNull<Node<T>>>,len:usize,_marker:PhantomData<Box<Node<T>>>,}impl<T>LinkedList<T>{#[inline]unsafefnpush_front_node(&mutself,node:NonNull<Node<T>>){unsafe{(*node.as_ptr()).next = self.head;(*node.as_ptr()).prev = None;let node = Some(node);matchself.head{None => self.tail = node,Some(head) => (*head.as_ptr()).prev = node,}self.head = node;self.len += 1;}}#[inline]fnpop_front_node(&mutself) -> Option<Box<Node<T>>>{self.head.map(|node| unsafe{let node = Box::from_raw(node.as_ptr());self.head = node.next;matchself.head{None => self.tail = None,Some(head) => (*head.as_ptr()).prev = None,}self.len -= 1;
node
})}#[inline]unsafefnpush_back_node(&mutself,node:NonNull<Node<T>>){unsafe{(*node.as_ptr()).next = None;(*node.as_ptr()).prev = self.tail;let node = Some(node);matchself.tail{None => self.head = node,Some(tail) => (*tail.as_ptr()).next = node,}self.tail = node;self.len += 1;}}#[inline]fnpop_back_node(&mutself) -> Option<Box<Node<T>>>{self.tail.map(|node| unsafe{let node = Box::from_raw(node.as_ptr());self.tail = node.prev;matchself.tail{None => self.head = None,Some(tail) => (*tail.as_ptr()).next = None,}self.len -= 1;
node
})}#[inline]unsafefnunlink_node(&mutself,mutnode:NonNull<Node<T>>){let node = unsafe{ node.as_mut()};match node.prev{Some(prev) => unsafe{(*prev.as_ptr()).next = node.next},None => self.head = node.next,};match node.next{Some(next) => unsafe{(*next.as_ptr()).prev = node.prev},None => self.tail = node.prev,};self.len -= 1;}#[inline]unsafefnremove_node(&mutself,node:NonNull<Node<T>>) -> T{unsafe{self.unlink_node(node);let node = Box::from_raw(node.as_ptr());
node.element}}}impl<T>DefaultforLinkedList<T>{#[inline]fndefault() -> Self{Self::new()}}impl<T>LinkedList<T>{#[inline]#[must_use]pubconstfnnew() -> Self{LinkedList{head:None,tail:None,len:0,_marker:PhantomData,}}#[inline]#[must_use]pubfnis_empty(&self) -> bool{self.head.is_none()}#[inline]#[must_use]pubfnlen(&self) -> usize{self.len}#[inline]pubfnclear(&mutself){drop(LinkedList{head:self.head.take(),tail:self.tail.take(),len: mem::take(&mutself.len),_marker:PhantomData,});}#[inline]#[must_use]pubfncursor_front(&self) -> Option<Cursor<T>>{self.head.map(Cursor::new)}#[inline]#[must_use]pubfncursor_back(&self) -> Option<Cursor<T>>{self.tail.map(Cursor::new)}#[inline]pubfnpush_front(&mutself,elt:T) -> Cursor<T>{let node = Box::new(Node::new(elt));let node_ptr = NonNull::from(Box::leak(node));unsafe{self.push_front_node(node_ptr);}Cursor::new(node_ptr)}#[inline]pubfnpop_front(&mutself) -> Option<T>{self.pop_front_node().map(Node::into_element)}#[inline]pubfnpush_back(&mutself,elt:T) -> Cursor<T>{let node = Box::new(Node::new(elt));let node_ptr = NonNull::from(Box::leak(node));unsafe{self.push_back_node(node_ptr);}Cursor::new(node_ptr)}#[inline]pubfnpop_back(&mutself) -> Option<T>{self.pop_back_node().map(Node::into_element)}pubunsafefniter(&self) -> RawIter<T>{RawIter{head:self.head,len:self.len,}}}impl<T>DropforLinkedList<T>{fndrop(&mutself){structDropGuard<'a,T>(&'amutLinkedList<T>);impl<'a,T>DropforDropGuard<'a,T>{fndrop(&mutself){whileself.0.pop_front_node().is_some(){}}}let guard = DropGuard(self);while guard.0.pop_front_node().is_some(){}
mem::forget(guard);}}#[repr(transparent)]pubstructCursor<T>(NonNull<Node<T>>);impl<T>CloneforCursor<T>{#[inline]fnclone(&self) -> Self{*self}}impl<T>CopyforCursor<T>{}impl<T>PartialEqforCursor<T>{#[inline]fneq(&self,other:&Self) -> bool{self.0 == other.0}}impl<T>EqforCursor<T>{}impl<T>Cursor<T>{#[inline]fnnew(node:NonNull<Node<T>>) -> Self{Cursor(node)}#[inline]pubunsafefnelement<'a>(&self) -> &'aT{&(*self.0.as_ptr()).element}#[inline]pubunsafefnelement_mut<'a>(&mutself) -> &'amutT{&mut(*self.0.as_ptr()).element}#[inline]pubunsafefnmove_to_front(self,list:&mutLinkedList<T>){
list.unlink_node(self.0);
list.push_front_node(self.0);}#[inline]pubunsafefnmove_to_back(self,list:&mutLinkedList<T>){
list.unlink_node(self.0);
list.push_back_node(self.0);}#[inline]pubunsafefnunlink(self,list:&mutLinkedList<T>) -> T{
list.remove_node(self.0)}}pubstructRawIter<T>{head:Option<NonNull<Node<T>>>,len:usize,}impl<T>IteratorforRawIter<T>{typeItem = Cursor<T>;#[inline]fnnext(&mutself) -> Option<Cursor<T>>{ifself.len == 0{returnNone;}self.head.map(|node| {self.len -= 1;self.head = unsafe{(*node.as_ptr()).next};Cursor::new(node)})}#[inline]fnsize_hint(&self) -> (usize,Option<usize>){(self.len,Some(self.len))}}unsafeimpl<T:Send + Send>SendforLinkedList<T>{}unsafeimpl<T:Sync + Sync>SyncforLinkedList<T>{}unsafeimpl<T:Send + Send>SendforRawIter<T>{}unsafeimpl<T:Sync + Sync>SyncforRawIter<T>{}unsafeimpl<T:Send + Send>SendforCursor<T>{}unsafeimpl<T:Sync + Sync>SyncforCursor<T>{}}mod new {use std::alloc::alloc;use std::alloc::dealloc;use std::alloc::handle_alloc_error;use std::alloc::Layout;use std::marker::PhantomData;use std::mem;use std::ptr::NonNull;use std::ptr::{self};/// Intrusive doubly-linked pointers shared by every node and the sentinel.pubstructLinks{prev:*mutLinks,next:*mutLinks,}implLinks{/// Returns a `Links` with both pointers null.#[inline]constfnempty() -> Self{Links{prev: ptr::null_mut(),next: ptr::null_mut(),}}}/// A single list element: link pointers plus the stored value.////// `#[repr(C)]` guarantees `links` is the first field at offset 0, so a/// `*mut Links` obtained from traversing the list can always be reinterpreted/// as `*mut Node<T>` (and vice versa) - this is what lets `Cursor<T>` and the/// sentinel-based traversal in `push_front_node`/`unlink_node` operate on/// plain `Links` pointers without knowing `T`.#[repr(C)]pubstructNode<T>{links:Links,element:T,}/// A doubly-linked list with an internal free-list for node reuse.pubstructLinkedList<T>{sentinel:NonNull<Links>,free_head:*mutLinks,len:usize,_marker:PhantomData<Box<Node<T>>>,}impl<T>LinkedList<T>{/// Adds `node` to the front of the list.////// # Safety////// - `node` must point to a valid, currently unlinked `Links` belonging/// to this list's allocations (freshly allocated via `alloc_node`/`get_node`,/// or previously unlinked via `unlink_node`/`pop_*_node`).////// - The caller must not use `node` again as an "unlinked" node until it is/// unlinked again.#[inline]unsafefnpush_front_node(&mutself,node:NonNull<Links>){let s = self.sentinel.as_ptr();let n = node.as_ptr();unsafe{// SAFETY: `s` is the sentinel, always valid; `n` is valid per caller contract.let first = (*s).next;(*n).prev = s;(*n).next = first;(*first).prev = n;(*s).next = n;}}/// Adds `node` to the back of the list.////// # Safety////// Same contract as [`push_front_node`](Self::push_front_node).#[inline]unsafefnpush_back_node(&mutself,node:NonNull<Links>){let s = self.sentinel.as_ptr();let n = node.as_ptr();unsafe{// SAFETY: `s` is the sentinel, always valid; `n` is valid per caller contract.let last = (*s).prev;(*n).next = s;(*n).prev = last;(*last).next = n;(*s).prev = n;}}/// Removes and returns the node at the front of the list, if any.#[inline]fnpop_front_node(&mutself) -> Option<NonNull<Links>>{ifself.len == 0{returnNone;}let s = self.sentinel.as_ptr();let node = unsafe{// SAFETY: `self.len != 0`, so `(*s).next` points to a real node,// and its `next` (`second`) is either another real node or `s` itself.let node = (*s).next;let second = (*node).next;(*second).prev = s;(*s).next = second;
node
};self.len -= 1;// SAFETY: `node` was just read from a valid linked node, hence non-null.Some(unsafe{NonNull::new_unchecked(node)})}/// Removes and returns the node at the back of the list, if any.#[inline]fnpop_back_node(&mutself) -> Option<NonNull<Links>>{ifself.len == 0{returnNone;}let s = self.sentinel.as_ptr();let node = unsafe{// SAFETY: `self.len != 0`, so `(*s).prev` points to a real node.let node = (*s).prev;let before = (*node).prev;(*before).next = s;(*s).prev = before;
node
};self.len -= 1;// SAFETY: `node` was just read from a valid linked node, hence non-null.Some(unsafe{NonNull::new_unchecked(node)})}/// Unlinks `node` from the list, without deallocating or reading its element.////// # Safety////// `node` must point to a node currently linked into this list (not the sentinel).#[inline]unsafefnunlink_node(&mutself,node:NonNull<Links>){let n = node.as_ptr();unsafe{// SAFETY: caller guarantees `n` is linked, so `prev`/`next` point to// valid nodes (or the sentinel).let prev = (*n).prev;let next = (*n).next;(*prev).next = next;(*next).prev = prev;}}/// Pushes `node` onto the internal free list for reuse.////// # Safety////// `node` must be unlinked and its `element` must already be logically/// moved out (dropped or read via `ptr::read`) — this only reuses the/// `Links`/allocation, not the `T` storage.#[inline]unsafefnrecycle_node(&mutself,node:*mutLinks){let free_head = self.free_head;unsafe{// SAFETY: `node` is valid per caller contract; writing `next` does// not touch `element`.(*node).next = free_head;}self.free_head = node;}/// Reads the element out of `node` and recycles the node's storage.////// # Safety////// `node` must point to a node whose `element` is initialized and which is/// no longer linked into the list (already unlinked by the caller).#[inline]unsafefntake_element_and_recycle(&mutself,node:NonNull<Links>) -> T{let node_ptr = node.as_ptr()as*mutNode<T>;// SAFETY: caller guarantees `element` is initialized; reading it does// not run its destructor, so no double-drop.let element = unsafe{ ptr::read(&(*node_ptr).element)};// SAFETY: the element has been logically moved out; the node is safe to recycle.unsafe{self.recycle_node(node.as_ptr())};
element
}/// Unlinks `node` from the list and returns its element.////// # Safety////// `node` must point to a node currently linked into this list#[inline]unsafefnremove_node(&mutself,node:NonNull<Links>) -> T{// SAFETY: caller guarantees `node` is linked.unsafe{self.unlink_node(node)};self.len -= 1;// SAFETY: `node` was just unlinked, and its `element` is still initialized.unsafe{self.take_element_and_recycle(node)}}/// Drops every linked element and parks its node on the free list.////// Elements whose destructors panic are handled by the caller's/// drop-guard strategy; nodes are recycled even on the panic path.fndrop_nodes(&mutself){let s = self.sentinel.as_ptr();whileself.len != 0{unsafe{// SAFETY: `self.len != 0`, so `(*s).next` points to a real node.let node = (*s).next;let second = (*node).next;(*second).prev = s;(*s).next = second;let node_ptr = node as*mutNode<T>;// SAFETY: `#[repr(C)]` puts `links` at offset 0, so `node` is a// valid `Node<T>` whose `element` is still initialized.
ptr::drop_in_place(&mut(*node_ptr).element);// SAFETY: the element was just dropped; the node is unlinked.self.recycle_node(node);}self.len -= 1;}}/// Frees every entry on the internal free list.////// # Safety////// Must only be called when no other references into the free list exist/// (e.g. from `Drop`); each recycled node must have been allocated with/// `Layout::new::<Node<T>>()`.unsafefndrop_freelist(&mutself){letmut node = self.free_head;while !node.is_null(){// SAFETY: `node` is a non-null pointer previously pushed by// `recycle_node`, so it points to a valid `Node<T>` allocation.let next = unsafe{(*node).next};// SAFETY: `node` was allocated via the global allocator with this// exact layout (see `alloc_node`/`LinkedList::new`), and is not// used again afterward.unsafe{dealloc(node as*mutu8,Layout::new::<Node<T>>())};
node = next;}self.free_head = ptr::null_mut();}/// Returns a node holding `elt`: reuses a free-list node when one is/// available, otherwise allocates a fresh one.#[inline]fnget_node(&mutself,elt:T) -> *mutNode<T>{if !self.free_head.is_null(){let links = self.free_head;// SAFETY: `free_head` is non-null here and points to a recycled// node threaded via `recycle_node`.self.free_head = unsafe{(*links).next};let node = links as*mutNode<T>;// SAFETY: `#[repr(C)]` puts `links` at offset 0, and only the// `element` slot of a recycled node is stale; `links` is// rewritten by the push that follows.unsafe{ ptr::write(&mut(*node).element, elt)};
node
}else{Self::alloc_node(elt)}}/// Cold fallback of `get_node`: allocates a brand-new node.#[inline(never)]#[cold]fnalloc_node(elt:T) -> *mutNode<T>{Box::into_raw(Box::new(Node{links:Links::empty(),element: elt,}))}}impl<T>DefaultforLinkedList<T>{/// Creates an empty `LinkedList<T>`.#[inline]fndefault() -> Self{Self::new()}}impl<T>LinkedList<T>{/// Creates an empty `LinkedList`.#[inline]#[must_use]pubfnnew() -> Self{let layout = Layout::new::<Links>();let raw = unsafe{let p = alloc(layout)as*mutLinks;if p.is_null(){handle_alloc_error(layout);}// SAFETY: `p` was just allocated with `layout` and checked non-null// above; writing a fresh self-referential `Links` into it is a// valid initialization of that memory.
ptr::write(p,Links{prev: p,next: p });
p
};// SAFETY: `p` was checked non-null (or diverged via `handle_alloc_error`).let sentinel = unsafe{NonNull::new_unchecked(raw)};LinkedList{
sentinel,free_head: ptr::null_mut(),len:0,_marker:PhantomData,}}/// Returns `true` if the list contains no elements.#[inline]#[must_use]pubfnis_empty(&self) -> bool{self.len == 0}/// Returns the number of elements in the list.#[inline]#[must_use]pubfnlen(&self) -> usize{self.len}/// Removes all elements, dropping each element's value.#[inline]pubfnclear(&mutself){self.drop_nodes();}/// Returns a cursor to the front element, or `None` if the list is empty.#[inline]#[must_use]pubfncursor_front(&self) -> Option<Cursor<T>>{ifself.len == 0{returnNone;}let s = self.sentinel.as_ptr();// SAFETY: sentinel is always valid; `self.len != 0` guarantees// `(*s).next` points to a real, linked node rather than back to `s`.let node = unsafe{(*s).next};// SAFETY: `node` is non-null (established above) and, by `#[repr(C)]`// on `Node<T>`, `*mut Links` and `*mut Node<T>` share the same address.Some(Cursor(unsafe{NonNull::new_unchecked(node as*mutNode<T>)}))}/// Returns a cursor to the back element, or `None` if the list is empty.#[inline]#[must_use]pubfncursor_back(&self) -> Option<Cursor<T>>{ifself.len == 0{returnNone;}let s = self.sentinel.as_ptr();// SAFETY: sentinel is always valid; `self.len != 0` guarantees// `(*s).prev` points to a real, linked node.let node = unsafe{(*s).prev};// SAFETY: same reasoning as in `cursor_front`.Some(Cursor(unsafe{NonNull::new_unchecked(node as*mutNode<T>)}))}/// Inserts `elt` at the front of the list and returns a cursor to it.#[inline]pubfnpush_front(&mutself,elt:T) -> Cursor<T>{let node = self.get_node(elt);// SAFETY: `get_node` always returns a non-null, freshly-usable `Node<T>` pointer.let links = unsafe{NonNull::new_unchecked(node as*mutLinks)};// SAFETY: `links` refers to a node that was just obtained (allocated// or recycled) and is not linked into any list yet.unsafe{self.push_front_node(links)};self.len += 1;// SAFETY: `node` is the same non-null pointer validated above.Cursor(unsafe{NonNull::new_unchecked(node)})}/// Removes and returns the front element, or `None` if the list is empty.#[inline]pubfnpop_front(&mutself) -> Option<T>{let node = self.pop_front_node()?;// SAFETY: `node` was just unlinked by `pop_front_node`, so its// `element` is still initialized and it is safe to take/recycle.let element = unsafe{self.take_element_and_recycle(node)};Some(element)}/// Inserts `elt` at the back of the list and returns a cursor to it.#[inline]pubfnpush_back(&mutself,elt:T) -> Cursor<T>{let node = self.get_node(elt);// SAFETY: `get_node` always returns a non-null, freshly-usable `Node<T>` pointer.let links = unsafe{NonNull::new_unchecked(node as*mutLinks)};// SAFETY: `links` refers to a node that was just obtained and is// not linked into any list yet.unsafe{self.push_back_node(links)};self.len += 1;// SAFETY: `node` is the same non-null pointer validated above.Cursor(unsafe{NonNull::new_unchecked(node)})}/// Removes and returns the back element, or `None` if the list is empty.#[inline]pubfnpop_back(&mutself) -> Option<T>{let node = self.pop_back_node()?;// SAFETY: `node` was just unlinked by `pop_back_node`, so its// `element` is still initialized and it is safe to take/recycle.let element = unsafe{self.take_element_and_recycle(node)};Some(element)}/// Returns a raw, unsynchronized iterator over cursors into the list.////// # Safety////// The caller must not mutate or drop the list while the returned/// `RawIter` (or any `Cursor` obtained from it) is in use, and must not/// call [`Cursor::unlink`], [`Cursor::move_to_front`], or/// [`Cursor::move_to_back`] on a yielded cursor while iteration is still/// in progress, since that would invalidate `RawIter::next`.#[inline]pubunsafefniter(&self) -> RawIter<T>{let s = self.sentinel.as_ptr();// SAFETY: sentinel is always valid, regardless of `self.len`.let next = unsafe{(*s).next};RawIter{
next,len:self.len,_marker:PhantomData,}}}// Guard ensures that if dropping an element panics, we still free// the free-list allocations instead of leaking them (the sentinel// is freed unconditionally below regardless of panics).structDropGuard<'a,T>(&'amutLinkedList<T>);impl<'a,T>DropforDropGuard<'a,T>{fndrop(&mutself){self.0.drop_nodes();// SAFETY: called only during unwind cleanup, after `drop_nodes`// has already run (or partially run); the free list is not// accessed again afterward.unsafe{self.0.drop_freelist()};}}impl<T>DropforLinkedList<T>{fndrop(&mutself){let guard = DropGuard(self);
guard.0.drop_nodes();// SAFETY: `drop_nodes` completed without panicking; the free list is// only touched here and then never again (guard is forgotten next).unsafe{ guard.0.drop_freelist()};
mem::forget(guard);// SAFETY: `self.sentinel` was allocated in `new` with exactly this// layout (`Layout::new::<Links>()`), and after this point `self` is// being destroyed, so the pointer is never dereferenced again.unsafe{dealloc(self.sentinel.as_ptr()as*mutu8,Layout::new::<Links>());}}}/// A handle to a single node in a `LinkedList`.////// `Cursor` is a thin, `Copy`able pointer to a node, obtained from/// [`LinkedList::push_front`], [`LinkedList::push_back`],/// [`LinkedList::cursor_front`], or [`LinkedList::cursor_back`], and used to/// later access or reposition that node via [`Cursor::element`],/// [`Cursor::move_to_front`], [`Cursor::move_to_back`], or [`Cursor::unlink`].////// `#[repr(transparent)]` over `NonNull<Node<T>>` means a `Cursor<T>` has the/// exact same layout as the raw pointer it wraps — no extra state is tracked,/// so the cursor does *not* know which `LinkedList` it came from, whether the/// node is still linked, or whether other `Cursor`s alias the same node. All/// of that is the caller's responsibility, which is why every non-trivial/// method on `Cursor` is `unsafe`.////// Because it is just a pointer, `Cursor<T>` is cheaply `Copy`/`Clone`, and/// equality (`PartialEq`/`Eq`) compares the underlying pointer, i.e. identity/// of the node, not the value of the element.#[repr(transparent)]pubstructCursor<T>(NonNull<Node<T>>);// `NonNull<Node<T>>` is just a pointer; copying it is always safe.impl<T>CloneforCursor<T>{#[inline]fnclone(&self) -> Self{*self}}impl<T>CopyforCursor<T>{}// Pointer equality: two cursors are equal if they point at the same node.impl<T>PartialEqforCursor<T>{#[inline]fneq(&self,other:&Self) -> bool{self.0 == other.0}}impl<T>EqforCursor<T>{}impl<T>Cursor<T>{/// Returns the underlying node as a `Links` pointer, for use with the/// list's internal `Links`-based operations.////// Relies on `#[repr(C)]` on `Node<T>` placing `links` at offset 0, so the/// cast is always valid regardless of `T`.#[inline]fnlinks(&self) -> NonNull<Links>{let ptr = self.0.as_ptr()as*mutLinks;// SAFETY: `self.0` is `NonNull`, so the reinterpreted pointer is non-null too.unsafe{NonNull::new_unchecked(ptr)}}/// Returns a reference to the node's element, with a caller-chosen lifetime.////// # Safety////// - The node this cursor points to must still be linked (or otherwise/// kept alive) in some `LinkedList<T>`, i.e. not yet unlinked/recycled.////// - The returned `&'a T` must not outlive the underlying allocation, and/// no `&mut T`/`element_mut` alias to the same node may exist while/// this reference is live.#[inline]pubunsafefnelement<'a>(&self) -> &'aT{let node = self.0.as_ptr();// SAFETY: caller guarantees the node is still allocated/linked and// that no conflicting `&mut` aliases the element.unsafe{&(*node).element}}/// Returns a mutable reference to the node's element, with a caller-chosen lifetime.////// # Safety////// Same contract as [`element`](Self::element), plus: no other reference/// (shared or mutable) to this node's element may be alive at the same time.#[inline]pubunsafefnelement_mut<'a>(&mutself) -> &'amutT{let node = self.0.as_ptr();// SAFETY: caller guarantees exclusive access to this node's element.unsafe{&mut(*node).element}}/// Moves the node this cursor points to the front of `list`.////// # Safety////// - The node must currently be linked into `list` (not some other list,/// and not already unlinked/recycled).////// - No other `Cursor`/reference into this node may be used concurrently/// with this call.#[inline]pubunsafefnmove_to_front(self,list:&mutLinkedList<T>){let links = self.links();// SAFETY: caller guarantees `links` is currently linked into `list`.unsafe{ list.unlink_node(links)};// SAFETY: `links` was just unlinked above, so it's safe to relink.unsafe{ list.push_front_node(links)};}/// Moves the node this cursor points to the back of `list`.////// # Safety////// Same contract as [`move_to_front`](Self::move_to_front).#[inline]pubunsafefnmove_to_back(self,list:&mutLinkedList<T>){let links = self.links();// SAFETY: caller guarantees `links` is currently linked into `list`.unsafe{ list.unlink_node(links)};// SAFETY: `links` was just unlinked above, so it's safe to relink.unsafe{ list.push_back_node(links)};}/// Removes the node this cursor points to from `list` and returns its element.////// # Safety////// - The node must currently be linked into `list`.////// - This consumes the cursor (`self`, by value) because the node is/// deallocated/recycled afterward — the cursor must not be used again.#[inline]pubunsafefnunlink(self,list:&mutLinkedList<T>) -> T{let links = self.links();// SAFETY: caller guarantees `links` is currently linked into `list`.unsafe{ list.remove_node(links)}}}/// A raw, unsynchronized iterator over `Cursor`s in a `LinkedList`.////// Created only via [`LinkedList::iter`], which is itself `unsafe` — see its/// `# Safety` section for the invariants that make walking `next`/`len` here/// sound (the list must not be mutated or dropped while this iterator, or any/// `Cursor` it yields, is in use).pubstructRawIter<T>{next:*mutLinks,len:usize,_marker:PhantomData<NonNull<Node<T>>>,}impl<T>IteratorforRawIter<T>{typeItem = Cursor<T>;#[inline]fnnext(&mutself) -> Option<Cursor<T>>{ifself.len == 0{returnNone;}let node = self.next;self.len -= 1;// SAFETY: `self.len != 0` (checked above) guarantees `node` is a// currently-valid, linked node, so `(*node).next` is a valid read;// per `LinkedList::iter`'s contract, the list is not mutated/dropped// while this iterator is alive.self.next = unsafe{(*node).next};// SAFETY: `node` is non-null (came from a valid linked `Links`), and// by `#[repr(C)]` on `Node<T>` a `*mut Links` is a valid `*mut Node<T>`.let cursor = Cursor(unsafe{NonNull::new_unchecked(node as*mutNode<T>)});Some(cursor)}#[inline]fnsize_hint(&self) -> (usize,Option<usize>){(self.len,Some(self.len))}}unsafeimpl<T:Send + Send>SendforLinkedList<T>{}unsafeimpl<T:Sync + Sync>SyncforLinkedList<T>{}unsafeimpl<T:Send + Send>SendforRawIter<T>{}unsafeimpl<T:Sync + Sync>SyncforRawIter<T>{}unsafeimpl<T:Send + Send>SendforCursor<T>{}unsafeimpl<T:Sync + Sync>SyncforCursor<T>{}}constFILL:usize = 4096;constCAP:usize = 1024;#[derive(Clone,Copy)]pubstructElem{puba:u64,pubb:u64,pubc:u64,pubd:u64,pube:u64,}implElem{#[inline]pubfnnew(seed:u64) -> Self{Elem{a: seed,b: seed.wrapping_mul(0x9E37_79B9_7F4A_7C15),c: seed.wrapping_add(0x1234_5678),d: seed.wrapping_mul(31),e: seed ^ 0xDEAD_BEEF,}}}macro_rules! impl_benches {($name:ident, $mod:ident) => {mod $name {usesuper::Bencher;usesuper::Elem;usesuper::CAP;usesuper::FILL;usecrate::$mod::Cursor;usecrate::$mod::LinkedList;fn fill(n:usize) -> LinkedList<Elem> {letmut l = LinkedList::new();for i in 0..n {
l.push_back(Elem::new(i asu64));}
l
}
#[divan::bench]fn lru_workload(bencher:Bencher){constOPS:usize = CAP*8;let bencher = bencher.counter(OPS);
bencher
.with_inputs(|| {letmut l = LinkedList::new();letmut table:Vec<Option<Cursor<Elem>>> = vec![None;CAP*2];for i in 0..CAP{
table[i] = Some(l.push_back(Elem::new(i asu64)));}(l, table)}).bench_values(|(mut l,mut table)| {letmut state:u64 = 0x9E37_79B9_7F4A_7C15;for _ in 0..OPS{
state = state
.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1_442_695_040_888_963_407);let key = ((state >> 33)asusize) % (CAP*2);ifletSome(c) = table[key]{unsafe{ c.move_to_back(&mut l)};} else {let evicted = l.pop_front();let evicted = unsafe{ evicted.unwrap_unchecked()};
table[evicted.a asusize] = None;
table[key] = Some(l.push_back(Elem::new(key asu64)));}}});}
#[divan::bench]fn evict_insert_cycle(bencher:Bencher){let bencher = bencher.counter(CAP);
bencher.with_inputs(|| fill(CAP)).bench_values(|mut l| {for i in 0..CAP{let _ = l.pop_front();
l.push_back(Elem::new(i asu64));}});}
#[divan::bench]fn move_to_back(bencher:Bencher){let bencher = bencher.counter(CAP);
bencher
.with_inputs(|| {let l = fill(CAP);let cursors:Vec<Cursor<Elem>> = unsafe{ l.iter().collect()};(l, cursors)}).bench_values(|(mut l, cursors)| {for c in cursors {unsafe{ c.move_to_back(&mut l)};}});}
#[divan::bench]fn pop_front_all(bencher:Bencher){let bencher = bencher.counter(FILL);
bencher.with_inputs(|| fill(FILL)).bench_values(|mut l| {whileletSome(e) = l.pop_front(){
divan::black_box(e.a);}});}
#[divan::bench]fn iterate(bencher:Bencher){let bencher = bencher.counter(FILL);
bencher.with_inputs(|| fill(FILL)).bench_values(|l| {letmut s:u64 = 0;unsafe{for c in l.iter(){
s = s.wrapping_add(c.element().a);}}
divan::black_box(s);});}}};}impl_benches!(upstream, old);impl_benches!(pr_version, new);fnmain(){
divan::main();}

Results (about ~2.7-2.8 times faster):

❯ cargo bench -- lru_workload Finished `bench` profile [optimized] target(s) in 0.04s Running bench.rs (target\release\deps\bench-eb9d257623e7ec19.exe)
Timer precision: 100 ns
bench fastest │ slowest │ median │ mean │ samples │ iters
├─ pr_version │ │ │ │ │
│ ╰─ lru_workload 60.99 µs │ 129.8 µs │ 64.09 µs │ 67.2 µs │ 100 │ 100
│ 134.2 Mitem/s │ 63.06 Mitem/s │ 127.8 Mitem/s │ 121.8 Mitem/s │ │
╰─ upstream │ │ │ │ │
╰─ lru_workload 227 µs │ 513.8 µs │ 283.7 µs │ 302.8 µs │ 100 │ 100
36.07 Mitem/s │ 15.94 Mitem/s │ 28.86 Mitem/s │ 27.05 Mitem/s │ │
❯ cargo bench -- lru_workload Finished `bench` profile [optimized] target(s) in 0.04s Running bench.rs (target\release\deps\bench-eb9d257623e7ec19.exe)
Timer precision: 100 ns
bench fastest │ slowest │ median │ mean │ samples │ iters
├─ pr_version │ │ │ │ │
│ ╰─ lru_workload 107.9 µs │ 179.9 µs │ 135.7 µs │ 135.8 µs │ 100 │ 100
│ 75.85 Mitem/s │ 45.51 Mitem/s │ 60.34 Mitem/s │ 60.28 Mitem/s │ │
╰─ upstream │ │ │ │ │
╰─ lru_workload 234.2 µs │ 437.5 µs │ 284.6 µs │ 292.9 µs │ 100 │ 100
34.96 Mitem/s │ 18.72 Mitem/s │ 28.77 Mitem/s │ 27.96 Mitem/s │ │
❯ cargo bench -- lru_workload Finished `bench` profile [optimized] target(s) in 0.03s Running bench.rs (target\release\deps\bench-eb9d257623e7ec19.exe)
Timer precision: 100 ns
bench fastest │ slowest │ median │ mean │ samples │ iters
├─ pr_version │ │ │ │ │
│ ╰─ lru_workload 104.2 µs │ 139 µs │ 110.1 µs │ 112.1 µs │ 100 │ 100
│ 78.54 Mitem/s │ 58.89 Mitem/s │ 74.37 Mitem/s │ 73.01 Mitem/s │ │
╰─ upstream │ │ │ │ │
╰─ lru_workload 242.2 µs │ 599.2 µs │ 357.8 µs │ 362.1 µs │ 100 │ 100
33.8 Mitem/s │ 13.66 Mitem/s │ 22.89 Mitem/s │ 22.61 Mitem/s │ │
❯ cargo bench -- lru_workload Finished `bench` profile [optimized] target(s) in 0.04s Running bench.rs (target\release\deps\bench-eb9d257623e7ec19.exe)
Timer precision: 100 ns
bench fastest │ slowest │ median │ mean │ samples │ iters
├─ pr_version │ │ │ │ │
│ ╰─ lru_workload 97.69 µs │ 134.8 µs │ 107.2 µs │ 108 µs │ 100 │ 100
│ 83.84 Mitem/s │ 60.72 Mitem/s │ 76.34 Mitem/s │ 75.84 Mitem/s │ │
╰─ upstream │ │ │ │ │
╰─ lru_workload 215.8 µs │ 489.6 µs │ 246.6 µs │ 288.1 µs │ 100 │ 100
37.94 Mitem/s │ 16.72 Mitem/s │ 33.2 Mitem/s │ 28.43 Mitem/s │ │

@awolverp

Copy link
Copy Markdown
Owner

Thanks 🔥

@chirizxc

Copy link
Copy Markdown
ContributorAuthor

I'll double-check all the SAFETY comments again a little later

@awolverp

Copy link
Copy Markdown
Owner

Aha OK

@awolverp

awolverp commented Aug 4, 2026

Copy link
Copy Markdown
Owner

This implementation is really good. I checked it.

But I think there's an issue
It's using an internal free list without any bound. I think it can consume a lot of memory because it won't free its memory until destruction.

We should remove the free list, or set a maximum length for it

@chirizxc

Copy link
Copy Markdown
ContributorAuthor

This implementation is really good. I checked it.

But I think there's an issue It's using an internal free list without any bound. I think it can consume a lot of memory because it won't free its memory until destruction.

We should remove the free list, or set a maximum length for it

Yes, thanks

@awolverp
awolverp merged commit 2204533 into awolverp:mainAug 5, 2026
12 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@chirizxc@awolverp