SyncUnsafeCell替换Mutex提高性能

1. 背景

在Rust开发过程中,很多情况下需要在不可变的情况下获取可变性或者在多线程的情况下可以安全的贡献可变数据。这种情况下我们一般使用**Mutex来实现通过加锁来实现。现在我们可以通过使用SyncUnsafeCell来替代Mutex**。

2. SyncUnsafeCell

SyncUnsafeCell 是 Rust 标准库中的一个类型,用于在多线程环境中安全地共享可变数据。它是 UnsafeCell 的一个包装,提供了额外的同步机制。

作用

  1. 共享可变数据:在 Rust 中,默认情况下,数据是不可变的,且不能在多个线程之间共享可变数据。SyncUnsafeCell 允许你在多线程环境中共享可变数据。
  2. 内部可变性SyncUnsafeCell 提供了内部可变性,这意味着你可以在不获取可变引用的情况下修改其内容。这对于需要在多线程环境中共享可变状态的场景非常有用。
  3. 安全性:虽然名字中包含 “Unsafe”,但 SyncUnsafeCell 在多线程环境中提供了一定程度的安全性。它通过内部的同步机制确保了对 UnsafeCell 的访问是安全的。

使用场景

  • 多线程共享状态:当你需要在多个线程之间共享可变状态时,可以使用 SyncUnsafeCell
  • 性能优化:在某些情况下,使用 SyncUnsafeCell 可以比使用 MutexRwLock 等同步原语更高效,因为它提供了更细粒度的控制。

3. SyncUnsafeCell在Rocketmq-rust中的应用

在**TopicConfigManager** 功能模块宗,因为很多方法都是使用了不可变引用 &self 那么需要修改**data_version** 就必须使用可变引用。为了解决这个问题就使用**Mutex** 来实现。如下图:

image-20240630155428782

但是这种情况下每次获取可以变引用都需要进行加锁才能获取。而这里的同步性是可预见的。不存在数据竞争所以使用**SyncUnsafeCell来替换Mutex** 减少加锁带来的性能消耗。

image-20240630155933525

这里为什么不使用**UnsafeCell** 因为在rocketmq-rust项目中需要Sync也就是:

image-20240630160220034

4.SyncUnsafeCell和Mutex的bench表现测试

我们使用下面的代码进行测试(测试代码参照:https://github.com/mxsm/rocketmq-rust/blob/main/rocketmq-broker/benches/syncunsafecell_mut.rs):

#![feature(sync_unsafe_cell)]
use std::cell::SyncUnsafeCell;
use std::collections::HashSet;use criterion::criterion_group;
use criterion::criterion_main;
use criterion::Criterion;pub struct Test {pub a: SyncUnsafeCell<HashSet<String>>,pub b: parking_lot::Mutex<HashSet<String>>,
}impl Test {pub fn new() -> Self {Test {a: SyncUnsafeCell::new(HashSet::new()),b: parking_lot::Mutex::new(HashSet::new()),}}pub fn insert_1(&self, key: String) {unsafe {let a = &mut *self.a.get();a.insert(key);}}pub fn insert_2(&self, key: String) {let mut b = self.b.lock();b.insert(key);}pub fn get_1(&self, key: &str) -> String {unsafe {let a = &*self.a.get();a.get(key).unwrap().to_string()}}pub fn get_2(&self, key: &str) -> String {let b = self.b.lock();b.get(key).unwrap().as_str().to_string()}
}fn benchmark_insert_1(c: &mut Criterion) {let test = Test::new();c.bench_function("insert_1", |b| {b.iter(|| {test.insert_1("key".to_string());})});
}fn benchmark_insert_2(c: &mut Criterion) {let test = Test::new();c.bench_function("insert_2", |b| {b.iter(|| {test.insert_2("key".to_string());})});
}fn benchmark_get_1(c: &mut Criterion) {let test = Test::new();let key = String::from("test_key");// Insert key for the get benchmarkstest.insert_1(key.clone());c.bench_function("get_1", |b| {b.iter(|| {test.get_1("test_key");})});
}fn benchmark_get_2(c: &mut Criterion) {let test = Test::new();let key = String::from("test_key");// Insert key for the get benchmarkstest.insert_2(key.clone());c.bench_function("get_2", |b| {b.iter(|| {test.get_2("test_key");})});
}criterion_group!(benches,benchmark_insert_1,benchmark_insert_2,benchmark_get_1,benchmark_get_2
);
criterion_main!(benches);

测试命令:

cargo bench

执行结果:

image-20240630160952494

要比较 insert_1insert_2 方法的优劣,我们需要考虑以下几个方面:

  1. 执行时间:

    • insert_1: 平均执行时间约为 42.637 ns。
    • insert_2: 平均执行时间约为 54.484 ns。

    从执行时间上看,insert_1 明显比 insert_2 快。

  2. 性能变化:

    • insert_1: 性能提升了约 5.8%。
    • insert_2: 性能下降了约 2.5%。

    insert_1 的性能提升,而 insert_2 的性能下降。

  3. 异常值数量:

    • insert_1: 4个异常值,3个轻微异常,1个严重异常。
    • insert_2: 4个异常值,均为轻微异常。

    虽然两者的异常值数量相同,但 insert_1 的异常值有一个严重异常,而 insert_2 的异常值均为轻微异常。

综合来看,insert_1 方法在执行时间和性能提升方面明显优于 insert_2,但需要注意的是 insert_1 存在一个严重异常的情况。这表明在大多数情况下 insert_1 是更好的选择,但在某些极端情况下可能会有性能波动。

4.1 get_1 和 get_2 方法的比较

  1. 执行时间:

    • get_1: 平均执行时间约为 49.228 ns。
    • get_2: 平均执行时间约为 50.564 ns。

    从执行时间上看,get_1 稍微比 get_2 快。

  2. 异常值数量:

    • get_1: 7个异常值,1个轻微异常,6个严重异常。
    • get_2: 4个异常值,均为轻微异常。

    get_1 存在较多的严重异常值,而 get_2 异常值较少且均为轻微异常。

结论
  • 插入操作insert_1 是更好的选择,因为它的执行时间更短且性能提升显著。虽然存在一些严重异常,但整体表现优于 insert_2
  • 获取操作get_1 的平均执行时间比 get_2 快,但存在较多严重异常。如果系统对性能一致性要求较高,get_2 可能是更好的选择,因为它的异常值较少且均为轻微异常。

总的来说,如果追求平均性能且可以接受一定程度的性能波动,insert_1get_1 是较好的选择;如果追求性能的一致性,insert_2get_2 可能更适合。

image-20240630161655253

让我们分析 insert_1 基准测试的结果:

4.2 图表分析

左侧图表(密度图)
  • X轴:代表插入操作的平均时间(以纳秒为单位)。
  • Y轴:代表密度(密度单位)。
  • 蓝色区域:显示了每次迭代所花费时间的概率分布,密度图的高峰显示了最可能的时间范围。
  • 蓝色垂直线:代表平均时间。对于 insert_1 操作,平均时间大约为 42.637 ns。
右侧图表(总样本时间)
  • X轴:表示迭代次数(以百万次为单位)。
  • Y轴:表示总样本时间(以毫秒为单位)。
  • 蓝色点:显示了总样本时间的线性回归,表明每次迭代的时间是相对恒定的。
附加统计数据
  • Slope:斜率,估算的每次迭代所需时间。
    • 下限:42.590 ns
    • 估算值:42.637 ns
    • 上限:42.686 ns
  • R²:决定系数,表示数据拟合度。值越接近1,拟合度越高。
    • R²:0.9954266
  • Mean:平均时间。
    • 42.661 ns
  • Std. Dev.:标准差,表示数据的离散程度。
    • 391.47 ps
  • Median:中位数,表示数据的中间值。
    • 42.591 ns
  • MAD:中位绝对偏差,表示数据的波动性。
    • 219.56 ps
结论
  • 平均时间insert_1 操作的平均时间为 42.637 ns。这表明操作的时间开销非常小。
  • 一致性:高 R² 值(0.9954266)表明基准测试结果非常一致,操作时间非常稳定。
  • 波动性:标准差(391.47 ps)和中位绝对偏差(219.56 ps)都非常小,表明操作时间波动很小。

总的来说,insert_1 操作性能非常稳定且高效,时间开销非常小。

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

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

相关文章

K8S之网络深度剖析(一)(持续更新ing)

K8S之网络深度剖析 一 、关于K8S的网络模型 在K8s的世界上,IP是以Pod为单位进行分配的。一个Pod内部的所有容器共享一个网络堆栈(相当于一个网络命名空间,它们的IP地址、网络设备、配置等都是共享的)。按照这个网络原则抽象出来的为每个Pod都设置一个IP地址的模型也被称作为I…

SpringBoot(一)创建一个简单的SpringBoot工程

Spring框架常用注解简单介绍 SpringMVC常用注解简单介绍 SpringBoot&#xff08;一&#xff09;创建一个简单的SpringBoot工程 SpringBoot&#xff08;二&#xff09;SpringBoot多环境配置 SpringBoot&#xff08;三&#xff09;SpringBoot整合MyBatis SpringBoot&#xff08;四…

3.ROS串口实例

#include <iostream> #include <ros/ros.h> #include <serial/serial.h> #include<geometry_msgs/Twist.h> using namespace std;//运行打开速度控制插件&#xff1a; rosrun rqt_robot_steering rqt_robot_steering //若串口访问权限不够&#xff1a…

详解PEFT库中LoRA源码

前言 GitHub项目地址Some-Paper-CN。本项目是译者在学习长时间序列预测、CV、NLP和机器学习过程中精读的一些论文&#xff0c;并对其进行了中文翻译。还有部分最佳示例教程。如果有帮助到大家&#xff0c;请帮忙点亮Star&#xff0c;也是对译者莫大的鼓励&#xff0c;谢谢啦~本…

读书笔记-《Spring技术内幕》(三)MVC与Web环境

前面我们学习了 Spring 最核心的 IoC 与 AOP 模块&#xff08;读书笔记-《Spring技术内幕》&#xff08;一&#xff09;IoC容器的实现、读书笔记-《Spring技术内幕》&#xff08;二&#xff09;AOP的实现&#xff09;&#xff0c;接下来继续学习 MVC&#xff0c;其同样也是经典…

Spring底层原理之bean的加载方式八 BeanDefinitionRegistryPostProcessor注解

BeanDefinitionRegistryPostProcessor注解 这种方式和第七种比较像 要实现两个方法 第一个方法是实现工厂 第二个方法叫后处理bean注册 package com.bigdata1421.bean;import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.…

解决idea中git无法管理项目中所有需要管理的文件

点击文件->设置 选择版本控制—>目录映射 点击加号 设置整个项目被Git管理

MySQL高级-事务-并发事务演示及隔离级别

文章目录 0、四种隔离级别1、创建表 account2、修改当前会话隔离级别为 read uncommitted2.1、会出现脏读 3、修改当前会话隔离级别为 read committed3.1、可以解决脏读3.2、会出现不可重复读 4、修改当前会话隔离级别为 repeatable read&#xff08;默认&#xff09;4.1、解决…

【论文阅读】transformer及其变体

写在前面&#xff1a; transformer模型已经是老生常谈的一个东西&#xff0c;以transformer为基础出现了很多变体和文章&#xff0c;Informer、autoformer、itransformer等等都是顶刊顶会。一提到transformer自然就是注意力机制&#xff0c;变体更是数不胜数&#xff0c;一提到…

【目标检测】DN-DETR

一、引言 论文&#xff1a; DN-DETR: Accelerate DETR Training by Introducing Query DeNoising 作者&#xff1a; IDEA 代码&#xff1a; DN-DETR 注意&#xff1a; 该算法是在DAB-DETR基础上的改进&#xff0c;在学习该算法前&#xff0c;建议掌握DETR、DAB-DETR等相关知识…

VMamba: Visual State Space Model论文笔记

文章目录 VMamba: Visual State Space Model摘要引言相关工作Preliminaries方法网络结构2D-Selective-Scan for Vision Data(SS2D) VMamba: Visual State Space Model 论文地址: https://arxiv.org/abs/2401.10166 代码地址: https://github.com/MzeroMiko/VMamba 摘要 卷积神…

axios的基本使用和vue脚手架自带的跨域问题解决

axios的基本使用和vue脚手架自带的跨域问题解决 1. axios 1.1 导入axios npm i axios1.2 创建serve1.js serve1.js const express require(express) const app express()app.use((request,response,next)>{console.log(有人请求服务器1了);console.log(请求来自于,re…

go Channel 原理 (一)

Channel 设计原理 不要通过共享内存的方式进行通信&#xff0c;而是应该通过通信的方式共享内存。 在主流编程语言中&#xff0c;多个线程传递数据的方式一般都是共享内存。 Go 可以使用共享内存加互斥锁进行通信&#xff0c;同时也提供了一种不同的并发模型&#xff0c;即通…

AI奏响未来乐章:音乐界的革命性变革

AI在创造还是毁掉音乐 引言 随着科技的飞速发展&#xff0c;人工智能&#xff08;AI&#xff09;正在逐渐渗透到我们生活的每一个角落&#xff0c;音乐领域也不例外。AI技术的引入&#xff0c;不仅为音乐创作、教育、体验带来了革命性的变革&#xff0c;更为整个音乐产业注入了…

顺序表应用——通讯录

在本篇之前的顺序表专题我们已经学习的顺序表的实现&#xff0c;了解了如何实现顺序表的插入和删除等功能&#xff0c;那么在本篇当中就要学习基于顺序表来实现通讯录&#xff0c;在通讯录当中能实现联系人的增、删、查改等功能&#xff0c;接下来就让我们一起来实现通讯录吧&a…

grpc学习golang版( 五、多proto文件示例 )

系列文章目录 第一章 grpc基本概念与安装 第二章 grpc入门示例 第三章 proto文件数据类型 第四章 多服务示例 第五章 多proto文件示例 第六章 服务器流式传输 第七章 客户端流式传输 第八章 双向流示例 文章目录 一、前言二、定义proto文件2.1 公共proto文件2.2 语音唤醒proto文…

基于局域网下的服务器连接、文件传输以及内网穿透教程 | 服务器连接ssh | 服务器文件传输scp | 内网穿透frp | 研究生入学必备 | 深度学习必备

&#x1f64b;大家好&#xff01;我是毛毛张! &#x1f308;个人首页&#xff1a; 神马都会亿点点的毛毛张 &#x1f4cc;本篇博客分享的是基于局域网下的服务器连接&#x1f517;、文件传输以及内网穿透教程&#xff0c;内容非常完备✨&#xff0c;涵盖了在服务器上做深度学…

树莓派3B读写EEPROM芯片AT24C256

AT24C256是一个Atmel公司的EEPROM存储芯片&#xff0c;容量是256K个bit&#xff08;也就是32K字节&#xff09;&#xff0c;I2C接口&#xff0c;而树莓派正好有I2C接口&#xff0c;如下图蓝框中的4个IO口&#xff0c; 把AT24C256和这4个口接在一起&#xff0c;这样硬件就准备好…

Django 页面展示模型创建表的数据

1&#xff0c;添加视图函数 Test/app8/urls.py from django.shortcuts import render from .models import Userdef create_user(request):if request.method POST:username request.POST.get(username)email request.POST.get(email)# ... 获取其他字段的值# 创建用户实例…

【Python学习篇】Python实验小练习——异常处理(十三)

个人名片&#xff1a; &#x1f393;作者简介&#xff1a;嵌入式领域优质创作者&#x1f310;个人主页&#xff1a;妄北y &#x1f4de;个人QQ&#xff1a;2061314755 &#x1f48c;个人邮箱&#xff1a;[mailto:2061314755qq.com] &#x1f4f1;个人微信&#xff1a;Vir2025WB…