为什么解除引用`Arc <t>,其中T:MyTrait`对齐到0x10?

时间:2017-02-19 20:39:07

标签: assembly rust x86-64 micro-optimization

我过度微优化了我的库,我正在查看生成的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(每晚设置+发布并点击ASM)

有问题的ASM代码是:

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

这项操作尽可能快。由于ASM解除引用是5个指令,其中3个似乎可能是不必要的,我想知道为什么会发生这种情况,如果我可以帮助它。也许我只是不明白这里的汇编指令。

编辑:我的最小示例并不完全相同,因为看起来需要使用crate boundary来阻止编译器/链接器优化该序列。但是在我的情况下,序列完全相同,在紧密(生锈工作台)循环中,没有涉及析构函数:只有Arc<TraitObject>上的方法调用。

2 个答案:

答案 0 :(得分:1)

该指令序列(至少在我运行它时)位于函数_ZN33_$LT$alloc..arc..Arc$LT$T$GT$$GT$9drop_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))
    }
}

序列是找到data ArcInner<T>成员的偏移量,其定义为(粗略地):

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包含数据指针和vtable指针,以及vtable starts with destructor, size, and alignment

更新:通过dpc.dw的其他研究/回答纠正我的理解。

data memeber需要针对类型T进行适当调整。但是,由于我们通过Arc<Trait>访问此内容,此时编译器并不知道该对齐是什么!例如,我们可能存储了一个64字节对齐的理论SIMD类型。但是,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)

答案 1 :(得分:1)

感谢Chris Emerson的回答,我意识到它与vtable和对齐规则有关。然后我询问了Mozilla的Rust IRC频道,aatch和talchas想出来了:

T将始终计算ArcInner<T>中存储的数据(struct)的对齐偏移量 - 因为实现T的每个<li> switch on/off<input data-role="switch" checked="checked" data-change="onChange"/> </li> 可能会有所不同。这不是什么大问题 - 因为这些指令速度非常快,并且会受到良好的指令级CPU限制。