Существует ли стандартный способ циклически вращающихся изменяемых переменных в Rust? - PullRequest
0 голосов
/ 29 июня 2019

Очень распространенной операцией при реализации алгоритмов является циклическое вращение: если, скажем, 3 переменные a, b, c изменяют их с эффектом

t ⇽ c
c ⇽ b
b ⇽ a
a ⇽ t

Учитывая, что все может быть заменено по битам, циклическое вращениедолжна быть область, в которой Rust превосходит больше, чем любой другой из известных мне языков.

Для сравнения, в C ++ наиболее эффективный общий способ поворота N элементов - это выполнение n + 1 std::move операций, что в свою очередь примерноприводит к (для типичной реализации конструктора перемещения) 3 (n+1) sizeof(T) присваиванию слов (это может быть улучшено для POD с помощью шаблона, специализирующегося на повороте, но требует работы).

В Rust язык позволяет реализовать поворот столько (n+1) size_of(T) словосочетаний.К моему удивлению, я не смог найти поддержку стандартной библиотеки для ротации.(Нет метода поворота в std::mem).Вероятно, это выглядело бы следующим образом:

pub fn rotate<T>(x: &mut T, y: &mut T, z: &mut T) {
    unsafe {
        let mut t: T = std::mem::uninitialized();
        std::ptr::copy_nonoverlapping(&*z, &mut t, 1);
        std::ptr::copy_nonoverlapping(&*y, z, 1);
        std::ptr::copy_nonoverlapping(&*x, y, 1);
        std::ptr::copy_nonoverlapping(&t, x, 1);
        std::mem::forget(t);
    }
}

Для пояснения того, почему вращение не может быть эффективно реализовано в C ++, рассмотрим:

struct String {
  char *data1;
  char *data2;
  String(String &&other) : data1(other.data1), data2(other.data2)
    { other.data1 = other.data2 = nullptr;}
  String &operator=(String &&other)
    { std::swap(data1, other.data1); std::swap(data2, other.data2);
      return *this; }
  ~String() { delete [] data1; delete [] data2; }
};

Здесь такая операция, как s2 = std::move(s1); будетвзять 3 назначения указателя для каждого поля элемента, всего 6 назначений, так как для замены указателя требуется 3 назначения (1 во временную, 1 вне временной, одно между операндами)

1 Ответ

4 голосов
/ 29 июня 2019

Существует ли стандартный способ циклически вращающихся изменяемых переменных в Rust?

Нет.

Я бы просто дважды поменял переменные, без необходимости unsafe:

use std::mem;

pub fn rotate<T>(x: &mut T, y: &mut T, z: &mut T) {
    mem::swap(x, y);
    mem::swap(y, z);
}

fn main() {
    let mut a = 1;
    let mut b = 2;
    let mut c = 3;

    println!("{}, {}, {}", a, b, c);
    // 1, 2, 3

    rotate(&mut a, &mut b, &mut c);

    println!("{}, {}, {}", a, b, c);
    // 2, 3, 1
}

Это дает 7 movl инструкций (Rust 1.35.0, Release, x86_64, Linux)

playground::rotate:
    movl    (%rdi), %eax
    movl    (%rsi), %ecx
    movl    %ecx, (%rdi)
    movl    %eax, (%rsi)
    movl    (%rdx), %ecx
    movl    %ecx, (%rsi)
    movl    %eax, (%rdx)
    retq

В отличие от оригинального 6 movl Инструкция:

playground::rotate_original:
    movl    (%rdx), %eax
    movl    (%rsi), %ecx
    movl    %ecx, (%rdx)
    movl    (%rdi), %ecx
    movl    %ecx, (%rsi)
    movl    %eax, (%rdi)
    retq

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


В «реальном» коде яиспользовать тот факт, что все переменные имеют одинаковый тип и существуют slice::rotate_left и slice::rotate_right:

fn main() {
    let mut vals = [1, 2, 3];

    let [a, b, c] = &vals;
    println!("{}, {}, {}", a, b, c);
    // 1, 2, 3

    vals.rotate_left(1);

    let [a, b, c] = &vals;
    println!("{}, {}, {}", a, b, c);
    // 2, 3, 1
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...