Rust的Vec优化


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

alt

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<[u325]> = 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 多平台发布

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/235351.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

编程之外,生活的美好航程

&#x1f680; 作者主页&#xff1a; 有来技术 &#x1f525; 开源项目&#xff1a; youlai-mall &#x1f343; vue3-element-admin &#x1f343; youlai-boot &#x1f33a; 仓库主页&#xff1a; Gitee &#x1f4ab; Github &#x1f4ab; GitCode &#x1f496; 欢迎点赞…

Linux常用命令----rmdir命令

文章目录 1. 简介2. 参数含义3. 常见用法及实例4. 注意事项5. 结语 1. 简介 rmdir命令在Linux系统中用于删除空的目录。它是一个基本的命令行工具&#xff0c;用于维护文件系统的组织结构。与rm命令不同&#xff0c;rmdir只能删除空目录&#xff0c;这提供了一种安全机制&…

堆栈_队列实现栈

//请你仅使用两个队列实现一个后入先出&#xff08;LIFO&#xff09;的栈&#xff0c;并支持普通栈的全部四种操作&#xff08;push、top、pop 和 empty&#xff09;。 // // 实现 MyStack 类&#xff1a; // // // void push(int x) 将元素 x 压入栈顶。 // int pop() 移除…

linux系统编程--文件描述符

在linux的世界里一切皆是文件。可以使用系统调用中I/O的函数&#xff08;(l: input&#xff0c;输入; O: output&#xff0c;输出&#xff09;&#xff0c;对文件进行相应操作&#xff08;open()、close()、write() 、read()等&#xff09;。 打开现存文件或者新建文件&#xf…

简历上的工作经历怎么写

通过了简历筛选&#xff0c;后续的面试官会仔细阅读你的简历内容。他们在找什么呢&#xff1f;他们希望搞清楚你在某一段经历中具体干了什么&#xff0c;并且判断你的能力具体达到了什么水平。 简历在线制作下载&#xff1a;百度幻主简历 面试官喜欢具体的经历 越具体&#x…

STM32CubeIDE(CUBE-MX)----快速移植FreeRTOS实战

文章目录 前言一、Freertos可视化配置二、生成代码三、实验现象总结 前言 FreeRTOS&#xff08;Real-Time Operating System&#xff09;是一个开源的实时操作系统内核&#xff0c;专注于嵌入式系统。它提供了一套用于管理任务、调度器、内存管理等的实时操作系统功能&#xf…

WSDM 2024 | LLMs辅助基于内容的推荐系统增强BPR训练数据

本文提出了一种简单而有效的基于LLMs的图数据增强策略&#xff0c;称为LLMRec&#xff0c;以增强基于内容的推荐系统。LLMRec包含三种数据增强策略和两种去噪策略。数据增强策略包括从文本自然语言的角度挖掘潜在的协同信号, 构建用户画像(LLM-based), 并强化item side informa…

滴滴2023.11.27P0级故障技术复盘回顾(k8s的的错?)

本文从滴滴官方恢复及技术公众号带大家从技术角度复盘这次事故 目录 1. 背景 2. 滴滴官方消息 3. 问题分析及定位 4.网传的k8s及解析 5.k8s引发的思考&#xff1a;举一反三&#xff0c;怎么避免再次出现 6.近段时间其他平台崩溃回顾 1. 背景 11 月 27 晚约 10 点&#xf…

vue 表格虚拟滚动

参考未整理 1.使用vxetable实现 官网 问题&#xff1a; 实现了表格的虚拟滚动&#xff0c;但是单元格数据不自动换行了 &#xff0c;如下显示的... 然后在官网看到是这样的&#xff0c;那我不是白写。。。 解决&#xff1a; 1.包一层div2.再写个换行样式 <vxe-column …

Springboot如何快速生成分页展示以及统计条数

这是表结构&#xff1a; 前置知识&#xff1a; 分页查询公式&#xff08;&#xff09;&#xff1a; -- 推导一个公式 -- select * from emp -- order by empno -- limit 每页显示记录数 * (第几页-1)&#xff0c;每页显示记录数 统计条数公式&#xff1a; select count…

lv11 嵌入式开发 轮询与中断13

1 CPU与硬件的交互方式 轮询 CPU执行程序时不断地询问硬件是否需要其服务&#xff0c;若需要则给予其服务&#xff0c;若不需要一段时间后再次询问&#xff0c;周而复始 中断 CPU执行程序时若硬件需要其服务&#xff0c;对应的硬件给CPU发送中断信号&#xff0c;CPU接收到中…

Python协程技术:从Greenlet到async/await的异步编程探索

协程&#xff1a; ​ 协程&#xff0c;在Python中&#xff0c;协程是一种轻量级的并发编程方式&#xff0c;它允许在单个线程内实现多个独立的执行流。协程可以在不同的执行点之间进行切换&#xff0c;而无需依赖于操作系统的线程切换。这使得协程成为处理高并发和异步任务的有…