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多平台发布

相关推荐
Asthenia041224 分钟前
从零开始:Dockerfile 编写与 Spring Cloud 项目部署到 Docker Compose
后端
Asthenia04121 小时前
准备面试:Jenkins部署SpringCloudAlibaba微服务商城全攻略
后端
woniu_maggie1 小时前
SAP EXCEL DOI 详解
开发语言·后端·excel
uhakadotcom2 小时前
云计算与开源工具:基础知识与实践
后端·面试·github
Asthenia04122 小时前
零基础指南:在Linux上用Docker和Jenkins实现Spring Cloud微服务的CI/CD
后端
嘵奇2 小时前
深入解析 Spring Boot 测试核心注解
java·spring boot·后端
uhakadotcom2 小时前
BPF编程入门:使用Rust监控CPU占用
后端·面试·github
uhakadotcom3 小时前
GHSL-2024-252: Cloudflare Workers SDK 环境变量注入漏洞解析
后端·面试·github
uhakadotcom3 小时前
GHSL-2024-264_GHSL-2024-265: 了解 AWS CLI 中的正则表达式拒绝服务漏洞 (ReDoS)
后端·面试·github
Asthenia04123 小时前
Feign的协议和序列化是用的什么?
后端