Rust的Vec优化

本篇是对Rust编程语言17_Rust的Vec优化[1]学习与记录

MiniVec

https://crates.io/crates/minivec

复制代码
enum DataWithVec {
    // tag,uint64,8字节
    I32(i32),       //  4字节,但需内存对齐到8字节?
    F64(f64),       // 8字节
    Bytes(Vec<u8>), // 24字节
}

fn main() {
    println!(
        "DataWithVec这个Option类型占的内存空间为:{}字节",
        std::mem::size_of::<DataWithVec>()
    );
}

DataWithVec这个Option类型占的内存空间为:32字节

enum占用的栈内存大小=8+其中占内存最大的字段的内存

但当100个enum类型的数据中,有80%都是8字节数据,如f64,剩下的20%才是24字节的Vec,那占得比例:

复制代码
enum DataWithVec {
    // tag,uint64,8字节
    I32(i32),       //  4字节,但需内存对齐到8字节?
    F64(f64),       // 8字节
    Bytes(Vec<u8>), // 24字节
} // 32 byte

enum DataWithWithoutVec {
    // tag,uint64,8字节
    I32(i32), //  4字节,但需内存对齐到8字节?
    F64(f64), // 8字节
} //16 byte

fn main() {
    println!(
        "DataWithVec这个Option类型占的内存空间为:{}字节",
        std::mem::size_of::<DataWithVec>()
    );

    let ratio = (80 * std::mem::size_of::<DataWithWithoutVec>()) as f64
        / (100 * std::mem::size_of::<DataWithVec>()) as f64;

    println!("ratio:{}", ratio)
}

DataWithVec这个Option类型占的内存空间为:32字节
ratio:0.4

利用率只有40%

剩下60%的都被浪费掉了

怎样可以缩减其大小?

最直接的想法是 用指针

复制代码
pub enum DataWithBoxVec {
     // tag,uint64,8字节
    I32(i32),       //  4字节,但需内存对齐到8字节?
    F64(f64),       // 8字节
    Bytes(Box<Vec<u8>>), // 8字节
}// 16 byte

但这样会有性能问题

因为使用了二级指针(因为Vec里面也有一个指向data的指针),极有可能导致缓存命中率下降.需要再从内存中把数据取到缓存中

一次缓存缺失,会比缓存命中慢一个数量级

所以尽量不用二级指针

可以变成一级指针:

复制代码
struct MiniVec<T> {
    // len,capacity,T
    data: * mut(usize,usize,T)
} //类似C语言的柔性数组

struct MiniVec {
    // len,capacity,T
    data: * mut(usize,usize,u8)
}


impl MiniVec  {
    pub fn new()-> MiniVec {
        MiniVec { 
            data: // 8+8+一定数量的T
     }
    }
}

也可以用实现更具体更优的第三方库 minivec[2]

MiniVec大小就是8byte了

DataWithMiniVec就是16 byte了,比之前的32 byte减少了一倍

复制代码
struct MiniVec<T> {
    // len,capacity,T
    data: * mut(usize,usize,T)
}


enum DataWithMiniVec {
    I32(i32),       
    F64(f64),      
    Bytes(MiniVec<u8>), 
}

smallvec

https://crates.io/crates/smallvec

new的时候不会分配内存

复制代码
fn main() {
    let vec: Vec<u8> = Vec::new();

    assert_eq!(vec.capacity(), 0)
}

分配一次堆内存很昂贵,尽可能在栈上分配

当数量较少时,在栈上操作;元素数量较多时,才在堆上分配.比较有名的第三方库 smallVec

元素大小必须在编译期就确定,是个常数

有个阈值N.当元素数量小于N,则用栈内存.(上限 一般是几K到几M) 反之元素数量很多时,就要在堆上分配

Rust中的 MaybeUninit的作用及注意点

在 Rust 中,MaybeUninit<T> 是一个非常有用但需要谨慎使用的类型,它用于处理可能未初始化的内存。它是 Rust 标准库 std::mem 模块的一部分,提供了一种处理未初始化数据的安全方式。

MaybeUninit<T> 的主要用途是处理以下场景:

  1. 延迟初始化 :当你有一个类型 T,但你不想或无法立即初始化它时,可以使用 MaybeUninit<T>。这对于性能优化特别有用,尤其是在处理大型数组或复杂类型时。
  2. 避免不必要的初始化开销 :对于某些类型,其默认初始化可能是昂贵的(例如,大型数组的零初始化)。使用 MaybeUninit<T> 可以避免这种开销。
  3. 与 FFI 交互 :当与 C 语言接口进行交互时,你可能需要处理未初始化的内存或者由 C 代码初始化的内存。MaybeUninit<T> 在这种情况下非常有用。

注意点

使用 MaybeUninit<T> 需要特别小心,因为不当的使用可能会导致未定义行为(UB),包括内存泄漏和数据损坏。以下是一些重要的注意事项:

  1. 安全性 :访问 MaybeUninit<T> 的值之前必须确保它已被正确初始化。未初始化的内存访问是未定义行为。
  2. 初始化 :你必须确保在使用 MaybeUninit<T> 的值之前,它已被完全且正确地初始化。
  3. DropMaybeUninit<T> 本身不会自动调用其内部值的 drop 方法。如果 T 需要被适当地销毁,你需要手动调用 drop
  4. 内存泄漏 :如果你在 MaybeUninit<T> 中存储了需要手动管理的资源(例如,指向堆内存的指针),请确保适当地释放这些资源。

示例

下面是一个简单的示例,演示了 MaybeUninit<T> 的基本使用:

复制代码
use std::mem::MaybeUninit;

fn main() {
    // 创建一个未初始化的实例
    let mut uninit_array: MaybeUninit<[u32; 5]> = MaybeUninit::uninit();

    // 安全地初始化数据
    let init_array = unsafe {
        let init_array = uninit_array.as_mut_ptr();
        for i in 0..5 {
            // 初始化数组的每个元素
            (*init_array)[i] = i as u32;
        }
        uninit_array.assume_init()
    };

    // 使用初始化后的数据
    println!("{:?}", init_array);
}

在这个例子中,创建了一个可能未初始化的数组,并在确保安全的情况下初始化它。请注意,使用 unsafe 块是必须的,因为我们在操作原始指针,并且假设初始化是安全的。不过,确保这种安全是开发者的责任。不恰当的使用 unsafe 可能会导致严重的错误。

bitVec

https://crates.io/crates/bitvec

bitVec 一般是用来存储bool类型的

一个bit就可以标识是true还是false

复制代码
struct BitVec {
    bits: Vec<u64>
}

VecOption

https://crates.io/crates/vec-option

该优化可有可无

复制代码
struct VecOption<T> {
    data: Vec<MaybeUninit<T>>,
    flag:BitVec,
}

当为Some时,像flag push一个true

使用时,先访问flag.

比如访问索引为3的,先看看flag[3]是true还是false,根据其值得出是Some还是None

参考资料

1

Rust编程语言17_Rust的Vec优化: https://www.bilibili.com/video/BV1pv4y12725

2

minivec: https://crates.io/crates/minivec

本文由mdnice多平台发布

相关推荐
javachen__1 分钟前
SpringBoot整合P6Spy实现全链路SQL监控
spring boot·后端·sql
uzong5 小时前
技术故障复盘模版
后端
GetcharZp6 小时前
基于 Dify + 通义千问的多模态大模型 搭建发票识别 Agent
后端·llm·agent
桦说编程6 小时前
Java 中如何创建不可变类型
java·后端·函数式编程
IT毕设实战小研6 小时前
基于Spring Boot 4s店车辆管理系统 租车管理系统 停车位管理系统 智慧车辆管理系统
java·开发语言·spring boot·后端·spring·毕业设计·课程设计
wyiyiyi7 小时前
【Web后端】Django、flask及其场景——以构建系统原型为例
前端·数据库·后端·python·django·flask
阿华的代码王国8 小时前
【Android】RecyclerView复用CheckBox的异常状态
android·xml·java·前端·后端
Jimmy8 小时前
AI 代理是什么,其有助于我们实现更智能编程
前端·后端·ai编程
AntBlack8 小时前
不当韭菜V1.1 :增强能力 ,辅助构建自己的交易规则
后端·python·pyqt
bobz9659 小时前
pip install 已经不再安全
后端