【问题标题】:Memory layout of Option<Box<T>> vs Box<Option<T>>. Which one is better?Option<Box<T>> 与 Box<Option<T>> 的内存布局。哪一个更好?
【发布时间】:2019-11-21 10:01:47
【问题描述】:

假设我有这两个结构

struct Node<T> {
    value: T,
    next: Box<Option<Node<T>>>
}

struct Node2<T> {
    value: T,
    next: Option<Box<Node2<T>>>
}

内存布局有什么区别?哪个更好?

【问题讨论】:

  • TLDR:那是一回事。 next 转换为指向Node 的指针:play.integer32.com/…
  • 这两个定义肯定有区别。 stackoverflow.com/questions/16504643/… 没有明确涵盖我认为的差异。 (例如,有明显的理由更喜欢Node2(使用Option&lt;Box&lt;...&gt;&gt; 的那个),但有趣的是Option&lt;Node&gt; 本身比Option&lt;Node2&gt; 小,因为前者有一个利基来存储判别式。)

标签: rust


【解决方案1】:

TL;DR:最好使用Node2,因为它可以避免在nextNone 的情况下进行堆分配。

为了进一步解释,让我们将问题简化为:Option&lt;Box&gt;Box&lt;Option&gt; 更好吗?

首先要注意的是,两种变体都只存储一个指针:

type VoidPtr = *const ();
let pointer_size = mem::size_of::<VoidPtr>();
assert_eq!(mem::size_of::<Option<Box<i32>>>(), pointer_size);
assert_eq!(mem::size_of::<Box<Option<i32>>>(), pointer_size);

这对于Box&lt;Option&gt; 来说并不奇怪,因为它实际上是一个指向Option 的指针。但是使用Box&lt;Option&gt;的缺点是即使值为None,Box也会进行堆分配。

// Although the Option is None, the pointer is not null,
// so a heap allocation is done to store None.
let none_box_option: Box<Option<i32>> = Box::new(None);
let pointer = &*none_box_option as *const Option<i32>;
assert!(pointer.is_null().not());

但是,Option&lt;Box&gt; 也只使用一个指针而不是例如一个指针加上一个布尔值来指示包含的值是Some 还是None,这可能会令人惊讶。 这里使用的技巧(至少在发布模式下)称为null pointer optimization。空指针用于指示None。每个其他值都是指定内存地址处的Some。这导致在值为 None 的情况下不必进行堆分配。相反,指针将设置为 null。

// A Option<Box> indicates None through a null pointer.
let none_option_box: Option<Box<i32>> = None;
assert!(as_bytes(&none_option_box).iter().all(|&byte| byte == 0));

// A Some Option<Box> simply holds the pointer to the inner value.
let some_option_box: Option<Box<i32>> = Some(Box::new(42));
let inner_bytes = as_bytes(&some_option_box);
let pointer = some_option_box.as_ref().unwrap().as_ref() as *const i32;
let pointer_bytes = as_bytes(&pointer);
assert_eq!(inner_bytes, pointer_bytes);

回到最初的问题:观察到的行为转移到NodeNode2。两者都需要相同数量的内存。

assert_eq!(mem::size_of::<Node<()>>(), mem::size_of::<Node2<()>>());
assert_eq!(mem::size_of::<Node<u64>>(), mem::size_of::<Node2<u64>>());
assert_eq!(mem::size_of::<Node<String>>(), mem::size_of::<Node2<String>>());

但是,如果nextNone,那么Node 将进行堆分配,而Node2 则不会。因此我建议使用Node2


Full code example

【讨论】:

    猜你喜欢
    • 2016-11-08
    • 1970-01-01
    • 1970-01-01
    • 2019-05-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-12
    • 1970-01-01
    相关资源
    最近更新 更多