#arena #arena-allocator #memory #individual #elements #drop #dropping

无需std drop_arena

一种单类型分配器,允许释放和回收单个元素

3个版本 (破坏性)

0.3.0 2024年8月16日
0.2.0 2023年8月17日
0.1.0 2023年5月18日

130内存管理

Download history 19/week @ 2024-07-29 152/week @ 2024-08-12

每月下载量171

MIT 许可证

41KB
402

drop_arena

简介

DropArena<T> 可以分配或释放类型 T 的单个元素。只有分配固定大小和对齐的元素,分配器才能与普通的 mallocfree 实现相比非常高效。将 DropArena 视为一个结合了 Arena 功能和创建盒子的分配器的功能。

DropArena 可以返回一个 DropBox<T>,它非常像 Box<T>,只是它与为其分配的 DropArena 相关联。可以使用 DropBox 来实现与 &mut T 相同的功能;实际上,它是一个围绕 &mut T 的透明包装器。

当涉及到移除一个 DropBox<T> 时,有几个选项。首先,您可以使用 drop(或者让 DropBox 超出作用域)。这将调用底层的 dropT),但它 不会 回收分配 T 所需的内存。同样,您可以使用 DropBox::into_inner,它提取底层的 T 但不会回收 T 原先占用的内存。当 DropArena<T> 本身被丢弃时,这些内存最终将被回收。最后,您可以调用 DropBox::leak,它产生一个 &mut T。这意味着除非使用不安全代码,否则 T 将永远不会被 drop。然而,当分配 DropBoxDropArena 被丢弃时,内存将被回收。

为了回收分配给 DropBox<T> 的内存,我们需要其分配它的 DropArena<T> 的引用。我们可以使用 DropBox::drop_box 方法在 DropBox 上丢弃底层值并回收内存。我们还可以使用 DropArena::box_into_inner 方法从 DropBox<T> 中检索底层 T 并回收其使用的内存。

为了确保竞技场只能回收它分配的 DropBox(或由具有相同生命周期的丢弃竞技场分配的)的内存,我们需要使用生命周期魔法。一个 DropArena 被标记为它将生活的生命周期,并且它与这个生命周期有一个不变的关系。 DropBox 与创建它的 DropArena 的生命周期有一个不变的关系。

不建议有多个具有相同生命周期的 DropArena<T>。特别是,如果竞技场 1 不断分配竞技场 2 持续消耗的 DropBox<T>,您将无法从回收内存中获得任何好处。然而,这样做是完全安全的。

复杂度

调用 DropArena::box_into_inner()DropBox::into_inner() 是 O(1) 并且具有非常小的常数(除非 T 的大小很大 - 那么 T 的复制占主导地位)。相应的 drop 函数也是 O(1) + 调用 Drop::drop 所需的时间,并且具有小的常数。

分配也非常快。分配有三种可能的路径。首先,区域中有之前分配过的空闲空间。在这种情况下,分配是 O(1) 并且常数很小。其次,区域预分配的容量足够大,可以容纳一个额外的元素。在这种情况下,分配也是 O(1) 并且常数很小。第三,区域真正用完了空间(这是最不常见的情况,即使我们只进行分配而不释放)。在这种情况下,我们必须使用系统分配器来分配更多空间。我们遵循与 typed_arena 相同的指南,进行一次足够容纳更多 T 的分配(实际上,我们使用 typed_arena 实现 DropArena)。

递归拥有数据结构

我们可以使用我们的区域编写一些基本拥有数据结构,如下所示。下面的列表实现受到了 使用完全过多的链表学习 Rust 的启发。

use drop_arena::{DropArena, DropBox};
struct Node<'arena, T> {
    item: T,
    rest: Link<'arena, T>,
}

type Link<'arena, T> = Option<DropBox<'arena, Node<'arena, T>>>;

struct List<'arena, T> {
    arena: &'arena DropArena<'arena, Node<'arena, T>>,
    ptr: Link<'arena, T>,
}

impl<'arena, T> Drop for List<'arena, T> {
    fn drop(&mut self) {
        while let Some(mut nxt) = self.ptr.take() {
            self.ptr = nxt.rest.take();
            self.arena.drop_box(nxt);
        }
    }
}

impl<'d, T> List<'d, T> {
    fn push(&mut self, val: T) {
        self.ptr = Some(self.arena.alloc(Node {
            item: val,
            rest: self.ptr.take(),
        }))
    }

    fn pop(&mut self) -> Option<T> {
        let first = self.ptr.take()?;
        let node = self.arena.box_into_inner(first);
        self.ptr = node.rest;
        Some(node.item)
    }

    fn new(arena: &'d DropArena<'d, Node<'d, T>>) -> Self {
        Self { arena, ptr: None }
    }
}


let arena = DropArena::new();
let mut list = List::new(&arena);

for i in 0..100 {
    list.push(i);
}

for i in (0..100).rev() {
    assert_eq!(list.pop().unwrap(), i);
}

改进领域

这个分配器适用于零大小类型,但在这种情况下效率不高。我计划在将来使用条件类型来解决这个问题。问题是,保持空闲块列表需要指针。然而,在理论上,当我们处理 ZST 时,我们可以选择根本不使用空闲列表。我想要单独使用 CondType 为 ZST 实现一个特殊的区域,但这个包仍然有限。为了使其可在这里使用,我们需要解决 这个问题

需要进行更多的测试以确保 DropArena 是安全的。我使用 Miri 进行了一些基本的实验,但需要彻底的模糊测试。此代码使用了相当多的 unsafe,这意味着有大量可能出现严重错误的机会。我认为我已经捕捉到了大多数错误,但也不排除我遗漏了某些错误。

确保 DropBox<T> 实现了所有 Box<T> 的特性似乎是可取的。

维护

这是我第一个开源项目,所以我可能没有时间来正确维护它。话虽如此,我会尽我最大的努力,时间允许的话,特别是对于严重的错误。请耐心等待。

许可:MIT

依赖关系

~53KB