When Rust Hurts

刘家财

2024-05-28. PDF

About ME

1. RIIR

Rewrite It In Rust

  • Leaving Rust gamedev after 3 years
    • 一旦你精通 Rust,所有这些问题都会消失
    • 但当你向我解释完我是如何错的时候,我已经完成了功能并继续前进了

2. 设计目标与核心理念

  • 性能(Performance)
    • 无 GC、无运行时
    • 零成本抽象
  • 可靠(Reliability)
    • 所有权机制
  • 生产力(Productivity)
    • 丰富的文档
    • 友好的编译器错误信息
    • 强大的工具链(cargo、clippy、rust-analyzer)

3. 所有权之痛

  • 一个作用域内,只能有一个可变引用或 N 个不可变引用
  • 一个对象在离开作用域时,自动释放内存
struct Container {
    items: Vec<i32>,
}

fn loop_items(&mut self) {
    for i in self.items.iter_mut() {
        self.do_something(i)
    }
}
fn do_something(&self, i: &i32) {}
 |         for i in self.items.iter_mut() {
 |                  ---------------------
 |                  |
 |                  mutable borrow occurs here
 |                  mutable borrow later used here
 |             self.do_something(i)
 |             ^^^^^^^^^^^^^^^^^^^^ immutable borrow occurs here

解决方案:内部可变性(RefCell、Mutex)

struct Container {
    items: RefCell<Vec<i32>>,
}

fn loop_items(&self) {
    for i in self.items.borrow_mut().iter_mut() {
        self.do_something(i)
    }
}

fn do_something(&self, i: &i32) { }
fn foo(s: &str, some_condition: bool) -> &str {
    if some_condition {
        &s.replace("foo", "bar")
    } else {
        s
    }
}
   |         &s.replace("foo", "bar")
   |         ^-----------------------
   |         ||
   |         |temporary value created here
   |         returns a reference to data owned by the current function
fn foo(s: &str, some_condition: bool) -> Cow<str> {
    if some_condition {
        Cow::from(s.replace("foo", "bar"))
    } else {
        Cow::from(s)
    }
}
  • Cow 在运行时决定类型是否为引用
  • 函数一般接受引用参数,返回具体对象

4. 异步之痛

协作式执行 asynchronous-multitasking-cooperative.webp

yield_now

Async 中避免长时间堵塞的逻辑!!! Tokio #4730

5. 编译之痛

  • 复杂的类型检查、所有权检查
  • 单态化(Monomorphization),Fast programs,slow compile times
  • 过程宏
  • 缺少 PImpl 支持,当一个 crate 改变时,下游需要全部编译,而不只是仅仅链接

编译单元(Codegen Units)

cargo build --timings
build-timings.webp

6. 内存分配之痛

程序的内存布局

申请 HEAP 的 API

//  syscall
int brk(void *addr);

void *mmap(void addr, size_t length, int prot, int flags,
           int fd, off_t offset);

// glibc
void *malloc( size_t size );
void free( void *ptr );
操作 耗时(纳秒)
syscall 100-300
funcall 1-2

Jason Evans' allocator(jemalloc)

分类 大小
Small [16, 32, 48, …, 128], [192, 256, 320, …, 512], [768, 1024, 1280, …, 3840]
Large [4 KiB, 8 KiB, 12 KiB, …, 4072 KiB]
Huge [4 MiB, 8 MiB, 12 MiB, …]

Memory Pool

struct Pool<T> {
    items: Vec<T>
}

fn pull(&mut self) -> T;
fn attach(&mut self, T);
  • crossbeam::queue::SegQueue
  • Arc

CPU 节约 10%!

高吞吐场景下的对象池

引用计数(RC) 👎
GC(sync.Pool 👍

Thanks

https://horaedb.apache.org/