C ++ is faster and safer than Rust, Yandex has taken measurements

Spoiler: C ++ is neither faster nor slower, and generally this is not the point. This article is a continuation of the glorious traditions of debunking the myths of large Russian companies about the Rust language. The previous one was " Go faster than Rust, Mail.Ru Group took measurements ."


Recently, I tried to lure a colleague, a sishnik from a neighboring department, to Darkside of Rust. But my conversation with a colleague did not work out. Because, quote:


In 2019, I was at the C ++ CoreHard conference , listened to Anton's reportantoshkkaPolukhina on the indispensable C ++. According to Anton, Rust is still young, not very fast and not so safe at all.

Anton Polukhin is the representative of Russia in ISO at international meetings of the working group on standardization C ++, the author of several accepted proposals for the standard for the C ++ language. Anton is really a cool and authoritative person in matters of C ++. But the report contains some serious factual errors regarding Rust. Let's take them apart.


We are talking about this report from 13:00 to 22:35 .



Table of contents




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


However, from recognized experts, I expect balanced coverage of the situation, which, at a minimum, does not contain gross factual errors.


Many thanks to Dmitry Kashitsyn and Alexei Kladov for reviewing the article.


All Articles