2017-02-19 61 views
3

Я по-микро-оптимизации моей библиотеки, и я смотрел на созданную ASM. Я заметил, что вызов метода Arc<T> where T : MyTrait вызывает то, что, по моему мнению, выравнивает указатель, хранящийся в ArcInner, до 0x10.Почему происходит разыменование `Arc <T>, где T: MyTrait` выровнены с 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 (Set Nightly + Release и нажмите ASM)

Код ASM в вопросе:

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

Это важно что эта операция должна быть как можно быстрее. Поскольку разыменование ASM - это 5 инструкций, и 3 из них кажутся потенциально ненужными, я хотел бы понять, почему это происходит, и если я могу помочь. Возможно, я просто не понимаю инструкции ассемблера.

Редактировать: Мой минимальный пример не совсем то же самое, поскольку он выглядит так, что граница ящика необходима, чтобы предотвратить компилятор/компоновщик для оптимизации этой последовательности. Но последовательность точно такая же в моем случае, в жесткой петле (ржавчины), нет деструкторов: только вызов метода на Arc<TraitObject>.

+1

Поскольку вы оптимизируете, вы знаете, что количество инструкций не обязательно соответствует скорости программы?Например, переход к векторизованным инструкциям может потребовать больше инструкций и все же обрабатывать больше байтов за цикл. –

ответ

1

Благодаря ответ Крис Эмерсон, я понял, что что-то делать с и центровки виртуальные таблицы правил. Тогда я спросил вокруг на канале Rust IRC Mozilla и aatch и talchas понял это:

rustc всегда будет вычислить выровнены смещение для данных (T), которые хранятся в ArcInner<T> - так как он может быть различным для каждого struct реализации T. Это не очень важно - так как эти инструкции очень быстрые, и они будут обладать хорошей параличностью CPU на уровне инструкций.

+1

Ах, да, спасибо - это имеет смысл! –

+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 starts with destructor, size, and alignment.

Обновление: исправьте свое понимание благодаря дополнительным исследованиям/ответам dpc.dw.

data memeber необходимо соответствующим образом выровнять по типу T. Однако, поскольку мы получаем доступ к этому через Arc<Trait>, на данный момент компилятор не знает, что это за выравнивание! Например, мы могли бы сохранить теоретический тип SIMD с выравниванием по 64 байтам. Однако указатель на объект жирного объекта содержит выравнивание, из которого можно вычислить смещение до 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) 
+0

Я думаю, что вы правы, и эти инструкции исходят из освобождения объекта-объекта. Мне придется повторно изучить мой исходный код и дважды проверить, я просто запутался в том, что эти инструкции были применены в 'kcachegrind'. –

+0

В конце концов, похоже, это не так в моей оригинальной программе. Деструктор там никогда не вызывается, но последовательность вычисления выравнивания есть. Мне кажется, что оптимизатор llvm не работает. –

+0

Может быть, для вызова метода для любого объекта объекта требуется выравнивание 0x10. например, для поиска vtable (или что-то в нем)? –