单入单出队列性能优化(Lock-Free)

摘要:文中首先介绍了有锁线程安全循环队列的基本实现,然后探讨了使用原子变量实现 Lock-Free 队列的优势,能够减少线程之间的数据竞争。接着,介绍了数据对齐的策略,以降低伪共享的概率,随后引入了索引缓存来减少索引访问冲突的影响。最后,文中提出了使用位运算替代模运算来优化循环队列的性能。
关键字:单入单出队列,Lock-Free,伪共享,索引缓存,性能优化

  CppCon中有一个单入单出Lock-Free队列的话题,描述了如何优化单入单出队列来达到性能最大化。该话题比较适合学习,因此基于该话题自己测试了下具体优化策略的性能。

1 线程安全循环队列

  ,我们先简单描述下有锁线程安全的队列的实现。首先是循环队列的实现。

在这里插入图片描述

  循环队列的实现比较简单。首先,申请一个固定大小的内存,也就是图示的capacity长度。然后维护两个游标inoutin是插入的位置,out是元素出队的位置。当in==out时,表示队列为空。为了方便表示队列满,队列的最后一块内存空闲出来,也就是当(in + 1) % capacity == out时队列满。

  入队时将插入值写入data[in],然后in=(in+1)%capacity即可(为了线程安全,我们操作前进行加锁)。

bool push(const value_type& v){std::unique_lock<std::mutex> lock(_mutex);if(full(0)){return false;}alloc_traits::construct(_alloc, _data + _in, v);_in = (_in + 1)%_cap;return true;
}

  出队时将data[out]处的元素出队并且销毁队列内存上的对象,out=(out+1)%capacity即可。

bool pop(value_type & v){std::unique_lock<std::mutex> lock(_mutex);if(empty(0)){return false;}v = std::move(_data[_out]);_data[_out].~value_type(); _out = (_out + 1)%_cap;return true;
}

2 Lock-Free

  多线程访问队列存在线程安全问题,为了保证线程安全,通过加锁来保证不同线程的安全性。但是锁的粒度太大,在访问冲突比较大的场景下,在临界区多线程依然是串行的,容易造成性能问题。因此一个优化思路是使用原子变量来实现Lock-Free的线程安全队列。Lock-Free的线程安全队列能够最小化不同线程之间的数据竞争,增加并发度。

bool push(const value_type& v){const auto inCur = _in.load(std::memory_order_relaxed);const auto outCur = _out.load(std::memory_order_acquire);if(full(inCur, outCur)){return false;}alloc_traits::construct(_alloc, _data + inCur, v);_in.store((inCur + 1) % capacity(), std::memory_order_release);return true;
}bool pop(value_type &val){const auto inCur = _in.load(std::memory_order_acquire);const auto outCur = _out.load(std::memory_order_relaxed);if (empty(inCur, outCur)) // (3)return false;val = std::move(_data[outCur]);alloc_traits::destroy(_alloc, _data + outCur);_out.store((outCur + 1) % capacity(), std::memory_order_release); // (4)return true;
}

  能够看到单入单出队列使用Lock-Free之后性能优化非常明显。
在这里插入图片描述

3 数据对齐

  数据对齐到CPU的Cacheline能够减低伪共享的概率。CPU 通常使用缓存来加速内存访问。数据在内存中是以缓存行(通常为 64 字节)为单位进行加载的。当一个线程修改一个变量时,整个缓存行会被标记为无效,这会影响到同一缓存行中的其他变量,即使这些变量并未被修改。尽管访问的是不同的变量,但由于它们共享同一个缓存行,不同线程的并发执行可能会导致频繁的缓存失效,进而影响性能。

  数据对齐的实现比较简单,下面代码中的pad是为了保证不同索引在不同的缓存行。

static constexpr auto hardware_destructive_interference_size = size_type{ 64 };
value_type* _data{};
char _pad0[hardware_destructive_interference_size]{};
alloc _alloc{};
char _pad1[hardware_destructive_interference_size]{};
alignas(hardware_destructive_interference_size) size_type _cap{};
char _pad2[hardware_destructive_interference_size]{};
alignas(hardware_destructive_interference_size) AtomicType _in{};
char _pad3[hardware_destructive_interference_size]{};
alignas(hardware_destructive_interference_size) AtomicType _out{};

在这里插入图片描述

4 索引缓存

  对于单入单出队列,如果push的时候如果上一次未满,这一次push也未满,pop同理,此时并不需要强行访问索引。因此可以增加索引缓存保存上一次索引的值,避免索引访问冲突而导致的CPU缓存刷新。但是需要注意的是这种方式只有索引竞争比较激烈的情况下才会有性能优化,否则可能有副作用。

bool push(const value_type& v) {const auto inCur = _in.load(std::memory_order_relaxed);if (full(inCur, _outCache)) {_outCache = _out.load(std::memory_order_acquire);if (full(inCur, _outCache)) {return false;}}alloc_traits::construct(_alloc, _data + inCur, v);_in.store((inCur + 1) % capacity(), std::memory_order_release);return true;
}bool pop(value_type& val) {const auto outCur = _out.load(std::memory_order_relaxed);if (empty(_inCache, outCur)) {_inCache = _in.load(std::memory_order_acquire);if (empty(_inCache, outCur)) {return false;}}val = std::move(_data[outCur]);alloc_traits::destroy(_alloc, _data + outCur);_out.store((outCur + 1) % capacity(), std::memory_order_release); // (4)return true;
}

  下面的数据带锁性能非常差,是因为大幅度降低队列的长度,增加缓存的竞争,导致带锁近乎于串行访问。
在这里插入图片描述

5 Mask

  引入Mask实际上是为了优化循环队列%的耗时。而mask是利用位运算替代除余。具体实现比较简单,只需要限制构造输入的cap一定是2^n,而实际的cap也就是下面的成员mask2^n-1,对应的二进制位0x111...111````。这样当队列满的时候只需要cur&mask```就能替代除余操作。

ThreadSafeQueueLockFreeAlignCacheMask(const size_type cap): _mask(cap - 1) {_data = alloc_traits::allocate(_alloc, cap);
}auto full(const size_type inCur, const size_type outCur) const {return ((inCur + 1) & _mask) == outCur;
}auto empty(const size_type inCur, const size_type outCur) const {return inCur == outCur;
}bool push(const value_type& v) {const auto inCur = _in.load(std::memory_order_relaxed);if (full(inCur, _outCache)) {_outCache = _out.load(std::memory_order_acquire);if (full(inCur, _outCache)) {return false;}}alloc_traits::construct(_alloc, _data + inCur, v);_in.store((inCur + 1) & _mask, std::memory_order_release);return true;
}bool pop(value_type& val) {const auto outCur = _out.load(std::memory_order_relaxed);if (empty(_inCache, outCur)) {_inCache = _in.load(std::memory_order_acquire);if (empty(_inCache, outCur)) {return false;}}val = std::move(_data[outCur]);alloc_traits::destroy(_alloc, _data + outCur);_out.store((outCur + 1) & _mask, std::memory_order_release); // (4)return true;
}

6 参考文献

  • Single Producer Single Consumer Lock-free FIFO From the Ground Up - Charles Frasch - CppCon 2023
  • Github: Single Producer Single Consumer Lock-free FIFO From the Ground Up - Charles Frasch - CppCon 2023

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

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

相关文章

java项目之网络游戏交易系统源码(ssm+mysql)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于ssm的网络游戏交易系统。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 本网络游戏交易系统分为管理员…

PyTorch 源码学习:GPU 内存管理之深入分析 CUDACachingAllocator

因引入 expandable_segments 机制&#xff0c;PyTorch 2.1.0 版本发生了较大变化。本文关注的是 PyTorch 原生的 GPU 内存管理机制&#xff0c;故研究的 PyTorch 版本为 2.0.0。代码地址&#xff1a; c10/cuda/CUDACachingAllocator.hc10/cuda/CUDACachingAllocator.cpp 更多内…

【PromptCoder】使用 package.json 生成 cursorrules

【PromptCoder】使用 package.json 生成 cursorrules 在当今快节奏的开发世界中&#xff0c;效率和准确性至关重要。开发者们不断寻找能够优化工作流程、帮助他们更快编写高质量代码的工具。Cursor 作为一款 AI 驱动的代码编辑器&#xff0c;正在彻底改变我们的编程方式。但如…

学习路程五 向量数据库Milvus操作

前序 前面安装好了docker且成功拉取Milvus镜像&#xff0c;启动。通过python成功连接上了数据。接下来就继续更多Milvus的操作 在开始之前&#xff0c;先来简单了解一下向量数据库内一些东西的基本概念 概念描述数据库&#xff08;Database&#xff09;类似与MySQL的database…

SpringBoot 热部署

1、添加 DevTools 依赖 <!-- 热部署依赖 --> <dependency> <groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId> </dependency>2、在IDEA的菜单栏中依次选择“File”→“Settings”&#x…

SOC-ATF 安全启动BL1流程分析(1)

一、ATF 源码下载链接 1. ARM Trusted Firmware (ATF) 官方 GitHub 仓库 GitHub 地址: https://github.com/ARM-software/arm-trusted-firmware 这是 ATF 的官方源码仓库&#xff0c;包含最新的代码、文档和示例。 下载方式&#xff1a; 使用 Git 克隆仓库&#xff1a; git…

汽车无钥匙进入一键启动操作正确步骤

汽车智能无钥匙进入和一键启动的技术在近年来比较成熟&#xff0c;不同车型的操作步骤可能略有不同&#xff0c;但基本的流程应该是通用的&#xff0c;不会因为时间变化而有大的改变。 移动管家汽车一键启动无钥匙进入系统通常是通过携带钥匙靠近车辆&#xff0c;然后触摸门把…

excel单、双字节字符转换函数(中英文输入法符号转换)

在Excel中通常使用函数WIDECHAR和ASC来实现单、双字节字符之间的转换。其中 WIDECHAR函数将所有的字符转换为双字节&#xff0c;ASC函数将所有的字符转换为单字节 首先来解释一下单双字节的含义。单字节一般对应英文输入法的输入&#xff0c;如英文字母&#xff0c;英文输入法…

IP----访问服务器流程

这只是IP的其中一块内容-访问服务器流程&#xff0c;IP还有更多内容可以查看IP专栏&#xff0c;前一段学习内容为IA内容&#xff0c;还有更多内容可以查看IA专栏&#xff0c;可通过以下路径查看IA-----配置NAT-CSDN博客CSDN,欢迎指正 1.访问服务器流程 1.分层 1.更利于标准化…

Ubutu部署WordPress

前言 什么是word press WordPress是一种使用PHP语言开发的建站系统&#xff0c;用户可以在支持PHP和MySQL数据库的服务器上架设WordPress。它是一个开源的内容管理系统&#xff08;CMS&#xff09;&#xff0c;允许用户构建动态网站和博客。现在的WordPress已经强大到几乎可以…

LangChain构建行业知识库实践:从架构设计到生产部署全指南

文章目录 引言:行业知识库的进化挑战一、系统架构设计1.1 核心组件拓扑1.2 模块化设计原则二、关键技术实现2.1 文档预处理流水线2.2 混合检索增强三、领域适配优化3.1 医学知识图谱融合3.2 检索结果重排序算法四、生产环境部署4.1 性能优化方案4.2 安全防护体系五、评估与调优…

Lua的table(表)

Lua表的基本概念 Lua中的表&#xff08;table&#xff09;是一种多功能数据结构&#xff0c;可以用作数组、字典、集合等。表是Lua中唯一的数据结构机制&#xff0c;其他数据结构如数组、列表、队列等都可以通过表来实现。 表的实现 Lua的表由两部分组成&#xff1a; 数组部分…

应对现代生活的健康养生指南

在科技飞速发展的现代社会&#xff0c;人们的生活方式发生了巨大改变&#xff0c;随之而来的是一系列健康问题。快节奏的生活、高强度的工作以及电子产品的过度使用&#xff0c;让我们的身体承受着前所未有的压力。因此&#xff0c;掌握正确的健康养生方法迫在眉睫。 针对久坐不…

使用DeepSeek/chatgpt等AI工具辅助网络协议流量数据包分析

随着deepseek,chatgpt等大模型的能力越来越强大&#xff0c;本文将介绍一下deepseek等LLM在分数流量数据包这方面的能力。为需要借助LLM等大模型辅助分析流量数据包的同学提供参考&#xff0c;也了解一下目前是否有必要继续学习wireshark工具以及复杂的协议知识。 pcap格式 目…

【Linux】CentOS7停服之后配置yum镜像源

&#x1f64b;大家好&#xff01;我是毛毛张! &#x1f308;个人首页&#xff1a; 神马都会亿点点的毛毛张 毛毛张今天分享一个CentOS7系统停服之后&#xff0c;配置yum镜像源的步骤&#xff0c;有坑&#xff01; 文章目录 1.概述2.查看系统架构2.1 查看内核版本2.2 查看lin…

2025-02-26 学习记录--C/C++-C语言 整数格式说明符

合抱之木&#xff0c;生于毫末&#xff1b;九层之台&#xff0c;起于累土&#xff1b;千里之行&#xff0c;始于足下。&#x1f4aa;&#x1f3fb; C语言 整数格式说明符 【例如 】&#x1f380; &#xff1a;在 C 语言中&#xff0c;%ld 是 printf 或 scanf 等格式化输入输出函…

OpenAI开放Deep Research权限,AI智能体大战升级,DeepSeek与Claude迎来新对决

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

个人电脑小参数GPT预训练、SFT、RLHF、蒸馏、CoT、Lora过程实践——MiniMind图文版教程

最近看到Github上开源了一个小模型的repo&#xff0c;是真正拉低LLM的学习门槛&#xff0c;让每个人都能从理解每一行代码&#xff0c; 从零开始亲手训练一个极小的语言模型。开源地址&#xff1a; GitHub - jingyaogong/minimind: &#x1f680;&#x1f680; 「大模型」2小时…

【数据结构】顺序表和链表

线性表 线性表 (linear list)是n个具有相同特性的数据元素的有限序列。线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串 ….. 线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时…

一文讲解Redis的内存淘汰和过期策略

Redis 报内存不足怎么处理&#xff1f; Redis 内存不足有这么几种处理方式&#xff1a; 修改配置文件 redis.conf 的 maxmemory 参数&#xff0c;增加 Redis 可用内存 也可以通过命令 set maxmemory 动态设置内存上限 修改内存淘汰策略&#xff0c;及时释放内存空间 使用 R…