Vec в 6 раз медленнее, чем Slice

#rust

Вопрос:

У меня было много проблем с rust, когда производительность моих программ была бы совершенно непредсказуемой и резко менялась бы при незначительных изменениях. Возьмем, к примеру, этот код ржавчины:

 use std::time::Instant;

#[derive(Clone, Copy, PartialEq, Eq)]
enum Foo {
    A,
    B,
    C,
}

struct Bar {
    arr: Vec<u32>,
}

impl Bar {
    pub fn new() -> Self {
        Self { arr: vec![0; 64] }
    }

    pub fn calc(amp;self, arr: amp;mut [Foo], pos: usize) -> (i32, i32) {
        let mut a = 0;
        let mut b = 0;

        let x = amp;self.arr[pos];
        for i in 0..10 {
            if arr[(pos   i) % 64] == Foo::A {
                a = 1;
            } else if arr[(pos   i) % 64] == Foo::B {
                b = 1;
            }
        }

        (a, b)
    }
}

fn main() {
    let bar = Bar::new();
    let mut state = vec![Foo::C; 64];

    let t1 = Instant::now();

    for _ in 0..1000000 {
        for pos in 0..64 {
            let x = bar.calc(amp;mut state, pos);
        }
    }

    println!("{}", t1.elapsed().as_millis());
}
 

В последней стабильной версии rust с режимом выпуска это занимает ~245 мс на моем i7 7700hq.
Однако измените fn calc объявление как таковое:

 pub fn calc(amp;self, arr: amp;mut Vec<Foo>, pos: usize) -> (i32, i32)
 

Я получаю 1550 мс. Более того, по ночам исходная функция занимает столько же времени, сколько и вторая, всего 100 мс.

Измените что-то незначительное , например, закомментируйте строку let x = amp;self.arr[pos]; , измените перечисление на int, измените функцию, которую нужно выполнить amp;mut self , и вы получите совершенно другой набор результатов, по-видимому, случайным образом.

Мне нравится rust, но я изо всех сил пытаюсь оправдать его использование по сравнению с c , когда я могу быть вынужден пытаться оптимизировать бессмысленными способами, когда он работает хуже в несколько раз в, казалось бы, случайных ситуациях. Эти тесты могут показаться синтетическими, но я извлек их из проблем, которые обнаружил в реальном коде. Исправьте этот пример, и я могу дать вам еще 10.

Комментарии:

1. Просто проверяю, так как вы явно не упомянули об этом, но используете --release ли вы его при выполнении теста? Если нет, то скорость не имеет значения, так как она не выполняла никаких оптимизаций.

2. @Locke да, я использую —release

3. Когда я пытаюсь запустить любую версию в режиме выпуска, она полностью оптимизирует вызов calc , так как это не имеет никакого эффекта. В конце концов он выводит 0 секунд для обоих вариантов на моей машине.

4. Если вы запустите cargo clippy , он уведомит вас о наиболее распространенных потенциальных недостатках производительности, таких как Vec прямая передача вместо среза. Передача ссылки на a Vec приведет к созданию двойного указателя, который требует дополнительного разыменования при каждом доступе. С другой стороны, срез-это жирный указатель, так что у вас нет этой проблемы.

5. Какую версию Rust вы используете? ( rustc --version ) Возможно, вы используете старую версию, которая плохо оптимизирует ваш код.