C ++ est plus rapide et plus sûr que Rust, Yandex a pris des mesures

Spoiler: C ++ n'est ni plus rapide ni plus lent, et généralement ce n'est pas le point. Cet article est une continuation des glorieuses traditions de démystification des mythes des grandes entreprises russes sur la langue rouille. Le précédent était " Allez plus vite que Rust, Mail.Ru Group a pris des mesures ."


Récemment, j'ai essayé d'attirer un collègue, un sishnik d'un département voisin, Sombrecôté de la rouille. Mais ma conversation avec un collègue n'a pas fonctionné. Parce que, citez:


En 2019, j'étais à la conférence C ++ CoreHard, j'ai écouté le rapport d'AntonantoshkkaPolukhina sur l'indispensable C ++. Selon Anton, Rust est encore jeune, pas très rapide et pas si sûr du tout.

Anton Polukhin est le représentant de la Russie à l'ISO lors des réunions internationales du groupe de travail sur la normalisation C ++, l'auteur de plusieurs propositions acceptées pour la norme pour le langage C ++. Anton est vraiment une personne cool et autoritaire en matière de C ++. Mais le rapport contient de sérieuses erreurs factuelles concernant Rust. Prenons-les à part.


Nous parlons de ce rapport de 13h00 Ă  22h35 .



Table des matières




â„–1. Rust C++.


(link:godbolt):


RustC++
pub fn square(num: i32) -> i32 {
    return num * num
}

auto square(std::int32_t num) {
    return num * num;
}

example::square:
  mov eax, edi
  imul eax, edi
  ret

square(int): # @square(int)
  mov eax, edi
  imul eax, edi
  ret


(13:35):


. ! . C++ Rust .

, , . , , . , Rust [-2147483648, 2147483647], C++ [-46340, 46340]. ? ?


-46340 46340 — , std::int32_t. - signed overflow. , PVS-Studio. , , CI , :


runtime error: signed integer overflow: 46341 * 46341 cannot be represented in type 'int'
runtime error: signed integer overflow: -46341 * -46341 cannot be represented in type 'int'

Rust .


, (13:58):


, C++ . . C++ . Rust' , . . Rust' , , , . , , Rust . - .

, Rust, , Rust LLVM — , Clang. , Rust «» C++ . , , . C++ . .


, int'a:


unsigned MAX_INT = 2147483647;

int hash_code(std::string x) {
    int h = 13;
    for (unsigned i = 0; i < 3; i++) {
        h += h * 27752 + x[i];
    }
    if (h < 0) h += MAX_INT;
    return h;
}

, , «bye», ( , ) . , , , MAX_INT .

PVS-Studio, . 27752 3 , , , - .


Rust (link:playground):


fn hash_code(x: String) -> i32 {
    let mut h = 13i32;
    for i in 0..3 {
        h += h * 27752 + x.as_bytes()[i] as i32;
    }
    if h < 0 {
        h += i32::max_value();
    }
    return h;
}

fn main() {
    let h = hash_code("bye".to_string());
    println!("hash: {}", h);
}

Debug Release , : wrapping*, saturating*, overflowing* checked*.


, .


— , C++ . . , , «» , .



â„–2. Rust .


(link:godbolt):


RustC++
pub fn foo(max: i32,
           num: i32) -> i32 {
    return max * num
}

pub fn bar(max: i32,
           num: i32) -> i32 {
    return bar(max, num) *
           bar(max, num)
}

auto foo(std::int32_t max,
         std::int32_t num) {
    return max * num;
}

std::int32_t bar(std::int32_t max,
                 std::int32_t num) {
    return bar(max, num) *
           bar(max, num);
}

example::foo:
  mov eax, edi
  imul eax, esi
  ret
example::bar:
  ret

foo(int, int): # @foo(int, int)
  mov eax, edi
  imul eax, esi
  ret
bar(int, int): # @bar(int, int)
  ret


(15:15):


Rust' C++ , bar . -, - . … , Rust , , UB — , , - . - , - . -- .

. - , NOP bar C++, Rust. LLVM.


LLVM IR , (link:godbolt):


code
#[no_mangle]
pub fn bar(max: i32, num: i32) -> i32 {
    return bar(max, num) * bar(max, num)
}

asm
bar:
    ret

IR
define i32 @bar(i32 %max, i32 %num) unnamed_addr #0 !dbg !5 {
start:
  ret i32 undef, !dbg !8
}


ret i32 undef — , LLVM.


LLVM 2006 . , , LLVM . , . LLVM 6 llvm.sideeffect, 2019 rustc -Z insert-sideeffect, llvm.sideeffect . (link:godbolt). , stable rustc .


C++ , LLVM Rust C.


, , LLVM, : " ". , Rust , , .



â„–3. Rust .


(16:00):


. Rust. bar foo. , Rust : - , . C++ . Rust . - .

(link:godbolt):


RustC++
fn foo(max: i32,
       num: i32) -> i32 {
    return max * num
}

pub fn bar(max: i32,
           num: i32) -> i32 {
    return foo(max, num) *
           foo(max, num)
}

auto foo(std::int32_t max,
         std::int32_t num) {
    return max * num;
}

std::int32_t bar(std::int32_t max,
                 std::int32_t num) {
    return foo(max, num) *
           foo(max, num);
}

example::bar:
    push    rax
    mov     eax, edi
    imul    eax, esi
    jo      .LBB0_1
    imul    eax, eax
    jo      .LBB0_5
    pop     rcx
    ret
.LBB0_1:
    lea     rdi, [rip + str.0]
    lea     rdx, [rip + .L...]
    jmp     .LBB0_2
.LBB0_5:
    lea     rdi, [rip + str.0]
    lea     rdx, [rip + .L...]
.LBB0_2:
    mov     esi, 33
    call    qword ptr [rip +..]
    ud2

bar(int, int): # @bar(int, int)
    mov     eax, edi
    imul    eax, esi
    jo      .LBB1_3
    imul    eax, eax
    jo      .LBB1_3
    ret
.LBB1_3:
    ud2


Rust , . -ftrapv C++ -C overflow-checks=on Rust, . C++ ud2, "Illegal instruction (core dumped)", Rust core::panicking::panic, . core::panicking::panic :


$ ./signed_overflow 
thread 'main' panicked at 'attempt to multiply with overflow', signed_overflow.rs:6:12
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

"" , ? x86-64 , 16 , call 8- , . , push rax. Rust, C++(link:godbolt):


RustC++
extern "C" { fn foo(); }

pub fn bar(max: i32,
           num: i32) -> i32 {
    let z = max * num;
    unsafe { foo(); }
    return z
}

extern "C" void foo();

std::int32_t bar(std::int32_t max,
                 std::int32_t num) {
    auto z = max * num;
    foo();
    return z;
}

example::bar:
    push    rbx
    mov     ebx, edi
    imul    ebx, esi
    jo      .LBB0_2
    call    qword ptr [rip + foo..]
    mov     eax, ebx
    pop     rbx
    ret

bar(int, int): # @bar(int, int)
    push    rbx
    mov     ebx, edi
    imul    ebx, esi
    jo      .LBB0_1
    call    foo
    mov     eax, ebx
    pop     rbx
    ret


C++, Rust , push rbx . Q.E.D.


, C++ -ftrapv, . , Rust -C overflow-checks=on, (link:godbolt) C++, . -ftrapv gcc 2008 .



â„–4. Rust C++.


(18:10):


Rust ...

, Rust', . , "" , 17:30(link:godbolt):


RustC++
pub struct Stats {
    x: u32,
    y: u32,
    z: u32,
}

pub fn sum(a: &Stats,
           b: &Stats) -> Stats {
    return Stats {
        x: a.x + b.x,
        y: a.y + b.y,
        z: a.z + b.z
    };
}

struct Stats {
    std::uint32_t x,
                  y,
                  z;
};

auto sum(const Stats& a,
         const Stats& b) {
    return Stats {
        a.x + b.x,
        a.y + b.y,
        a.z + b.z
    };
}

example::sum:
  mov ecx, dword ptr [rdx]
  mov r8d, dword ptr [rdx + 4]
  add ecx, dword ptr [rsi]
  add r8d, dword ptr [rsi + 4]
  mov edx, dword ptr [rdx + 8]
  add edx, dword ptr [rsi + 8]
  mov rax, rdi
  mov dword ptr [rdi], ecx
  mov dword ptr [rdi + 4], r8d
  mov dword ptr [rdi + 8], edx
  ret

sum(Stats const&, Stats const&):
  mov eax, dword ptr [rsi]
  add eax, dword ptr [rdi]
  mov ecx, dword ptr [rsi + 4]
  add ecx, dword ptr [rdi + 4]
  mov edx, dword ptr [rsi + 8]
  add edx, dword ptr [rdi + 8]
  shl rcx, 32
  or rax, rcx
  ret


, , , → .


2019 CppCon There Are No Zero-cost Abstractions Chandler Carruth. 17:30 - , std::unique_ptr (link:godbolt). - noexcept, rvalue , std::move. Rust . . Rust extern "Rust" unsafe, (link:godbolt):


RustC++
extern "Rust" {
    fn bar(k: &i32);
    fn baz(_: Box<i32>);
}

pub fn foo(mut ptr: Box<i32>) {
    if *ptr > 42 {
        unsafe { bar(&*ptr) };
        *ptr = 42;
    }
    unsafe { baz(ptr) }
}

void bar(int* ptr)
    noexcept;

// Takes ownership.
void baz(std::unique_ptr<int>&& ptr)
    noexcept;

void foo(std::unique_ptr<int>&& ptr) {
    if (*ptr > 42) {
      bar(ptr.get());
      *ptr = 42;
    }
    baz(std::move(ptr));
}

example::foo:
    push    rbx
    mov     rbx, rdi
    cmp     dword ptr [rdi], 43
    jl      .LBB0_2
    mov     rdi, rbx
    call    qword ptr [rip + bar..]
    mov     dword ptr [rbx], 42
.LBB0_2:
    mov     rdi, rbx
    pop     rbx
    jmp     qword ptr [rip + baz..]

foo(std::unique_ptr<...>&&):
    pushq   %rbx
    movq    %rdi, %rbx
    movq    (%rdi), %rdi
    cmpl    $43, (%rdi)
    jl      .LBB0_2
    callq   bar(int*)
    movq    (%rbx), %rax
    movl    $42, (%rax)
.LBB0_2:
    movq    %rbx, %rdi
    popq    %rbx
    jmp     baz(...)


Rust . noexcept, rvalue std::move. . , , .


2019 Rust C++ Benchmarks Game. C++ . . .



№5. C → ++ — noop, C → Rust — PAIN!!!!!!!


(18:30):


, Rust , . , , . ++ , . Rust' - .

.


, Rust , , . Starcraft, .


, Rust cargo, . , , . 2020 crates.io 40 000 .


:


# Cargo.toml
[dependencies]
flate2 = "1.0"

cargo . flate2 , miniz, C, Rust. flate2 .



â„–6. unsafe Rust.


(19:14):


unsafe Rust', , , .

Rust' .


, unsafe — , Rust , unsafe :


  1. ;
  2. unsafe ;
  3. ;
  4. unsafe ;
  5. union.

Rust . lifetime-, unsafe . , , - . You can’t "turn off the borrow checker" in Rust.


unsafe " , ". , , . , . , malloc NULL , Rust . , , , malloc, : " , ; , , ". unsafe.



â„–7. Rust .


(19:25):


C++ , , - - , - null . . Rust . - . Rust , , C++.

Microsoft, 70% , Rust . , Rust.


, unsafe Rust, , … , , . , , Rust .


, , , Rust C++ , Rust . Rust . , unsafe .


unsafe :


// Warning: Calling this method with an out-of-bounds index is undefined behavior.
unsafe fn unchecked_get_elem_by_index(elems: &[u8], index: usize) -> u8 {
    *elems.get_unchecked(index)
}

slice::get_unchecked — unsafe , . get_elem_by_index , , . unsafe(link:playground):


// Warning: Calling this method with an out-of-bounds index is undefined behavior.
unsafe fn unchecked_get_elem_by_index(elems: &[u8], index: usize) -> u8 {
    *elems.get_unchecked(index)
}

fn main() {
    let elems = &[42];
    let elem = unsafe { unchecked_get_elem_by_index(elems, 0) };
    dbg!(elem);
}

, , . unsafe.


, unsafe (link:playground):


// Warning: Calling this method with an out-of-bounds index is undefined behavior.
unsafe fn unchecked_get_elem_by_index(elems: &[u8], index: usize) -> u8 {
    *elems.get_unchecked(index)
}

fn get_elem_by_index(elems: &[u8], index: usize) -> Option<u8> {
    if index < elems.len() {
        let elem = unsafe { unchecked_get_elem_by_index(elems, index) };
        Some(elem)
    } else {
        None
    }
}

fn main() {
    let elems = &[42];
    let elem = get_elem_by_index(elems, 0);
    dbg!(&elem);
}

, . , Rust ( slice::get), , unsafe Rust . unchecked_get_elem_by_index , C.


LTO :


C
#include <stdint.h>

uint32_t mul(uint32_t a, uint32_t b) {
    return a * b;
}

Rust
#[link(name="mul", kind="static")]
extern {
    fn mul(a: u32, b: u32) -> u32;
}

#[inline(never)]
#[no_mangle]
fn multiply(a: u32, b: u32) -> u32 {
    unsafe { mul(a, b) }
}

fn main() {
    println!("42*42 = {}!", multiply(42,42));
    println!("87*31 = {}!", multiply(87,31));
}

asm
000000000002eda0 <multiply>:
   2eda0:   89 f8                   mov    %edi,%eax
   2eda2:   0f af c6                imul   %esi,%eax
   2eda5:   c3                      retq


. , C(link:godbolt), , Rust.



â„–8. Rust .


(20:38):


X. . X, , . . . . , X, .

2018 , Rust, , , . , unsafe safe , , .


, (), unsafe, , .


, Mutex, RwLock, thread::spawn. . , Rust ; , Mutex , , , . ? .




, . " - C++" , C++, .


Cependant, de la part d'experts reconnus, je m'attends à une couverture équilibrée de la situation, qui, au minimum, ne contient pas d'erreurs factuelles grossières.


Un grand merci Ă  Dmitry Kashitsyn et Alexei Kladov pour avoir relu l'article.


All Articles