我过度微优化了我的库,我正在查看生成的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>
上的方法调用。
答案 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限制。