C ++ ist schneller und sicherer als Rust, Yandex hat Messungen durchgefĂĽhrt

Spoiler: C ++ ist weder schneller noch langsamer, und im Allgemeinen ist dies nicht der Punkt. Dieser Artikel ist eine Fortsetzung der glorreichen Traditionen, die Mythen groĂźer russischer Unternehmen ĂĽber die Rust-Sprache zu entlarven. Der vorherige war " Gehen Sie schneller als Rust, Mail.Ru Group nahm Messungen vor ."


Kürzlich habe ich versucht, einen Kollegen, einen Sishnik aus einer benachbarten Abteilung, zu locken DunkelSeite von Rust. Aber mein Gespräch mit einem Kollegen hat nicht geklappt. Weil, Zitat:


2019 war ich auf der C ++ CoreHard-Konferenz und habe Antons Bericht angehörtAntoshkkaPolukhina über das unverzichtbare C ++. Laut Anton ist Rust noch jung, nicht sehr schnell und überhaupt nicht so sicher.

Anton Polukhin ist der Vertreter Russlands in der ISO bei internationalen Treffen der Arbeitsgruppe für Standardisierung C ++, der Autor mehrerer akzeptierter Vorschläge für den Standard für die Sprache C ++. Anton ist wirklich eine coole und maßgebliche Person in Sachen C ++. Der Bericht enthält jedoch einige schwerwiegende sachliche Fehler in Bezug auf Rust. Nehmen wir sie auseinander.


Wir sprechen ĂĽber diesen Bericht von 13:00 bis 22:35 Uhr .



Inhaltsverzeichnis




â„–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++, .


Von anerkannten Experten erwarte ich jedoch eine ausgewogene Berichterstattung über die Situation, die zumindest keine groben sachlichen Fehler enthält.


Vielen Dank an Dmitry Kashitsyn und Alexei Kladov fĂĽr die ĂśberprĂĽfung des Artikels.


All Articles