C ++ es más rápido y seguro que Rust, Yandex ha tomado medidas

Spoiler: C ++ no es ni más rápido ni más lento, y generalmente este no es el punto. Este artículo es una continuación de las gloriosas tradiciones de desacreditar los mitos de las grandes compañías rusas sobre el lenguaje Rust. El anterior fue " Ir más rápido que Rust, Mail.Ru Group tomó medidas ".


Recientemente, traté de atraer a un colega, un sishnik de un departamento vecino, a Oscurolado de óxido. Pero mi conversación con un colega no funcionó. Porque cita:


En 2019, estuve en la conferencia C ++ CoreHard , escuché el informe de AntonAntoshkkaPolukhina sobre el indispensable C ++. Según Anton, Rust todavía es joven, no es muy rápido y no es tan seguro en absoluto.

Anton Polukhin es el representante de Rusia en ISO en las reuniones internacionales del grupo de trabajo sobre estandarización C ++, el autor de varias propuestas aceptadas para el estándar para el lenguaje C ++. Anton es realmente una persona genial y autoritaria en materia de C ++. Pero el informe contiene algunos errores de hecho serios con respecto a Rust. Vamos a desarmarlos.


Estamos hablando de este informe de 13:00 a 22:35 .



Tabla de contenido




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


Sin embargo, de expertos reconocidos, espero una cobertura equilibrada de la situaciĂłn, que, como mĂ­nimo, no contiene errores de hecho graves.


Muchas gracias a Dmitry Kashitsyn y Alexei Kladov por revisar el artĂ­culo.


All Articles