为什么取消引用 `Arc<T> where T : MyTrait` 对齐到 0x10?

Why does dereferencing `Arc<T> where T : MyTrait` align to 0x10?

我对我的库进行了过度微优化,我正在查看生成的 ASM。我注意到调用 Arc<T> where T : MyTrait 的方法会产生一些我认为正在将存储在 ArcInner 中的指针与 0x10 对齐的东西。

我用这段代码复制了它:

#![feature(test)]
extern crate test;

use std::sync::Arc;

struct DA;

trait Drain {
    fn log(&self, &DA);
}

struct BlackBoxDrain;

impl Drain for BlackBoxDrain {
    fn log(&self, da: &DA) {
        test::black_box::<&DA>(da);
    }
}

fn f(d: Arc<Drain>) {
    d.log(&DA)
}

fn main() {
    let arc_d = Arc::new(BlackBoxDrain);
    f(arc_d);
}

Rust playground(设置 Nightly + Release 然后点击 ASM)

有问题的 ASM 代码是:

movq    16(%r15), %rdi
leaq    15(%rdi), %rax
negq    %rdi
andq    %rax, %rdi
addq    %r14, %rdi

重要的是这个操作要尽可能快。由于 ASM 取消引用是 5 条指令,其中 3 条似乎可能是不必要的,所以我想了解为什么会发生这种情况以及是否可以提供帮助。也许我只是不明白这里的汇编指令。

编辑:我的最小示例并不完全相同,因为看起来需要板条箱边界来防止 compiler/linker 优化该序列。但是在我的例子中,顺序是完全一样的,在一个紧密的(rust bench)循环中,没有涉及析构函数:只有一个方法调用 Arc<TraitObject>.

该指令序列(至少当我 运行 它时)在函数 _ZN33_$LT$alloc..arc..Arc$LT$T$GT$$GTdrop_slow17h09d36c48f370a93dE 中,它分解为 <alloc::arc::Arc<T>>::drop_slow。这是释放函数。 Looking at the source:

unsafe fn drop_slow(&mut self) {
    let ptr = *self.ptr;

    // Destroy the data at this time, even though we may not free the box
    // allocation itself (there may still be weak pointers lying around).
    ptr::drop_in_place(&mut (*ptr).data);

    if self.inner().weak.fetch_sub(1, Release) == 1 {
        atomic::fence(Acquire);
        deallocate(ptr as *mut u8, size_of_val(&*ptr), align_of_val(&*ptr))
    }
}

序列正在查找 ArcInner<T>data 成员的偏移量,其定义为(大致):

struct ArcInner<T: ?Sized> {
    strong: atomic::AtomicUSize,  // 64-bit or 8 byte atomic count
    weak: atomic::AtomicUsize,    // ditto
    data: T,                      // The actual data payload.
}

作为背景,trait object contains a data pointer and vtable pointer, and the vtable starts with destructor, size, and alignment

更新:由于 dpc.dw 的附加 research/answer.

更正了我的理解

data 成员需要根据 T 类型适当对齐。但是,由于我们是通过 Arc<Trait> 访问它的,此时编译器不知道对齐是什么!例如,我们可能已经存储了一个理论上的 SIMD 类型,具有 64 字节对齐。但是,fat trait 对象指针确实包含对齐,从中可以计算到 data 的偏移量。这就是这里发生的事情:

// assumption: rbx is pointer to trait object
movq    (%rbx), %r14       // Get the ArcInner data pointer into r14
movq    8(%rbx), %r15      // Get vtable pointer into r15
movq    16(%r15), %rdi     // Load T alignment from vtable into rdi
leaq    15(%rdi), %rax     // rax := align+15
negq    %rdi               // rdi = -rdi (-align)
andq    %rax, %rdi         // rdi = (align+15) & (-align)
addq    %r14, %rdi         // Add now aligned offset to `data` to call drop
callq   *(%r15)            // Call destructor (first entry in vtable in r15)

感谢 Chris Emerson 的回答,我意识到它与 vtable 和对齐规则有关。然后我在 Mozilla 的 Rust IRC 频道上四处询问,aatch 和 talchas 弄明白了:

rustc 将始终计算存储在 ArcInner<T> 中的数据 (T) 的对齐偏移量 - 因为每个 struct 实现 [=11] 的偏移量可能不同=].这没什么大不了的 - 因为这些指令非常快,并且会受到良好指令级别 CPU 并行化的影响。