读书笔记——C++高性能编程(六)

news2024/11/25 15:59:26

第六章.并发和性能

阿姆达尔定律

介绍了阿姆达尔定律(Amdahl's Law),这个定律的意义是“系统中对某一部件采用更快执行方式所能获得的系统性能改进程度,取决于这种执行方式被使用的频率”。具体的公式如下:

eq?s%3D%5Cfrac%7Bs_%7B0%7D%7D%7Bs_%7B0%7D%281-p%29+p%7D

其中s0是程序并行部分的加速比例,p是程序并行的部分。

举例说明:假设一个程序在8线程下运行,并行运行的比例是50%(8线程可以认为是8倍速度运行)。那么其加速效果是:

eq?s%3D%5Cfrac%7B8%7D%7B8*%281-0.5%29+0.5%7D%3D%5Cfrac%7B16%7D%7B9%7D%5Capprox%201.78

而当并行比例提高到80%的时候,加速比例能够达到3.33。这个结果说明这样两个事实:提高并行比例可以让程序运行的更快;多线程程序应该尽量少地访问共享数据。

原子操作

介绍了原子操作关于内存序是和原子操作实现相关的,而且原子操作请求自由内存序也不会使其更快。由于原子操作的指令集极其小,所以有些操作需要通过CAS来实现。原子操作的实现原理来自于CPU对cache-line的独占访问,这也意味着实际上原子操作也是一个不需要经过操作系统的“锁”。

介绍以下wait-free和lock-free:

std::atomic<size_t> count;
...在线程中...
count.fetch_add(1, std::memory_order_relaxed);

上面这个是一个wait-free的代码,仅仅是从代码层面看是不需要等待的(wait-free),但是如上所述,在机器的层面其还是会存在硬件层面的锁定。

std::atomic<size_t> count;
...线程中...
size_t c = count.load(std::memory_order_relaxed);
while (!count.compare_exchange_strong(c, c+1, std::memory_order_relaxed, std::memory_order_relaxed)){}

上面这个是lock-free的代码。这个里面可以看到,从代码层面,线程会有可能被困在while循环中,但是没有用到系统的锁操作,因此称为lock-free。

总结一下:

1)无等待:每个线程都在执行它需要的操作,并始终朝着最终目标前进;无需等待访问,无需重做任何操作。(也就是说不需要“试错-会滚-重做”的操作)

2)无锁:多个线程会共享一个值,但只有一个会成功,其余的将不得不根据原始值丢弃它们已经完成的工作,读取更新后的值,并再次进行计算。但是至少有一个线程总是可以保证提交其工作而不必重做。因此,整个程序总是在向前推进,尽管不一定是全速前进。

3)有锁:一个线程持有锁,但是可以不做任何事情(因为这里锁并没有提交的保证,而原子操作是有提交保证的)。并发发生时候,最多只有一个线程在向前推进,并且即使如此也不能保证它一定前进。

自旋锁

class Spinlock
{
public:
    void lock()
    {
        while(flag_.exchange(1, std::memory_order_acquire));
    }
    void unlock()
    {
        flag_.store(0, std::memory_order_release);
    }
private:
    std::atomic<unsigned int> flag_;
};

上面这个是自旋锁的简单实现,这里要插入讲以下的是内存序。可以看到,加锁用的是acquire,解锁用的是release。acquire会防止本线程的所有读操作移动到该行以后,也就意味着,写操作可以跑到前面去。release操作会保证所有的写操作都在该行之前完成。也就是说,在上锁处,所有的读操作已经完成,在解锁处所有的写操作已经完成。

还有一个知识点,cache-line的独占访问。看一下优化后的加锁代码:

class Spinlock
{
public:
    void lock()
    {
        while ( flag_.load(std::memory_order_relaxed)||flag_.exchange(1, std::memory_order_acquire));
    }
};

这个优化版本只是在原来上锁之前增加了一个load操作,为什么能够起到优化的效果呢?因为load不需要独占访问,上锁线程已经改变该值,各CPU缓存中是最新的值。此时如果调用exchange,则需要各个线程再锁定cache-line,如果有多个等待线程,则所有等待线程的访问不是并行的,而load是读,是可以多个等待线程并行的访问的。(当然我觉得这个优化毫无必要,只是多个线程更快地在自旋中消耗CPU资源,但是这个思想比较有意义)

还有一个细节,就是操作系统的偏好问题。操作系统更偏好于将CPU调度给占用大量CPU的线程,而自旋锁会占用大量的CPU,因此操作系统会更偏向于将CPU调度给正在等待的线程,从而导致等待释放自旋锁的线程没有CPU来执行释放。解决方法就是在尝试数次之后让等待线程sleep 1ns或者调用sched_yield来进行。作者实验是nanosleep效果更好。

作者实验了有锁/wait-free/lock-free/自旋锁,发现自增的情况下自旋锁速度更快。但是自旋锁有其问题,是其忙等消耗CPU的问题。系统锁其上锁成本很高,因此需要长的临界区来均摊上锁和解锁成本,而短且冲突不那么频繁的临界区,自旋锁通常效果更好。

锁和无锁的问题

系统锁会出现死锁/活锁/护送/优先级反转的问题,原子操作不会。但是原子操作的主要问题在于2点:1,上锁浪费大量CPU资源;2,原子操作对于数据同步可能有各种不同的状态,需要保证在所有这些不同的同步状态下程序的处理逻辑都正确。

自旋指针

自旋指针用于多线程访问同一个指针的情况,其原理和自旋锁一样,只不过不是处理数字而是指针。

template<typename T>
class PtrSpinLock
{
public:
    explicit PtrSpinlock(T* p):p_(p){}
    T* lock()
    {
        T*  p_cur = nullptr;
        while (!(p_cur = p_.exchange(nullptr, std::memory_order_acquire)));
        saved_p_ = p_cur;
        return p_cur;
    }
    void unlock()
    {
        p_.store(saved_p_, std::memory_order_release);
    }
private:
    std::atomic<T*> p_;
    T* saved_p_ = nullptr;
};

这个是对原书上的代码进行了一些修改。意思就是说在获取不到指针的时候就一直循环获取,直到获取到后将指针返回,并修改之前的指针值。(扩展一下,无所链表主要难点在于验证其尾指针的同时不能修改其next指针的值。可以用这个自旋指针,这样如果尾指针在修改的时候可以保证pop函数不能弹出其尾指针,从而能够修改其next值并放回)

发布协议

发布协议就是在一个用于保证多个线程访问同一个对象时候必须做到以保证线程安全的操作。考虑单生产者和单消费者,协议包括:

1)生产者独占正在准备的数据;2)使用者使用共享指针访问;3)使用者线程访问数据的唯一途径是通过根指针,并且该指针保持为空,直到生产者线程准备好显示或者发布数据。4)使用者随时通过原子方式查询,生产者在发布后不对发布的数据进行修改。

自己的一点思考:在无锁链表中,会存在头指针获取最后一个元素时尾指针正在插入的情况,是否可以通过发布协议的方式进行优化。

并发编程的智能指针

shared_ptr如果是多个线程的不同副本指向同一个对象则是线程安全的。如果是多个线程共享一个智能指针对象则是不安全的。因为对于同一个智能指针的成员的操作不是线程安全的。我们就想到有一个办法。在C++20中使用std::atomic<std::shared_ptr<T>>来保证复制的安全性,在复制完后在各个线程中的副本就安全了。

在不支持C++20的编译器上可以使用如下代码实现原子共享指针:

/// 设置 ///
std::shared_ptr<T> p_;
T* data = new T;
...完成data初始化...
std::atomic_store_explicit(&p_, std::shared_ptr<T>(data), std::memory_order_release);

/// 获取 ///
std::shared_ptr<T> p_;
const T* data = std::atomic_load_explicit(&p_, std::memory_order_acquire).get();

本章总结:

知道了阿姆达尔定律的意义,对于并行编程而言就是增加并行程度。学习了lock-free和wait-free的区别,这俩都是代码层面的,lock-free是用cas等待,wait-free就是原子操作。学习了共享指针的特性,了解了其效率很低的事实。知道了如何在线程间共享指针。

 

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

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

相关文章

基于CodeFormer使用C++实现图片模糊变清晰,去除马赛克等效果

前言 CodeFormer是一种基于AI技术深度学习的人脸复原模型&#xff0c;由南洋理工大学和商汤科技联合研究中心联合开发。该模型通过结合了VQGAN和Transformer等技术&#xff0c;可以通过提供模糊或马赛克图像来生成清晰的原始图像。可以实现老照片修复、照片马赛克修复、黑白照…

深入浅出ThreadPoolExecutor(一)

文章目录 线程池简诉ThreadPoolExecutor详解ThreadPoolExecutor参数详解创建线程池的工具类Executors 线程池简诉 针对各种池子,比如 连接池:用于管理和重复使用数据库连接&#xff0c;避免频繁创建和销毁数据库连接带来的性能开销。对象池&#xff1a;用于管理和重复使用对象…

中国雪深长时间序列数据集(1979-2020)

简介 中国雪深长时间序列数据集&#xff08;1979-2020&#xff09;提供1979年1月1日到2020年12月31日逐日的中国范围的积雪厚度分布数据&#xff0c;其空间分辨率为25km&#xff0c;是“中国雪深长时间序列数据集&#xff08;1978-2012&#xff09;”的升级版本。前言 – 人工…

5.Python-使用XMLHttpRequest对象来发送Ajax请求

题记 使用XMLHttpRequest对象来发送Ajax请求&#xff0c;以下是一个简单的实例和操作过程。 安装flask模块 pip install flask 安装mysql.connector模块 pip install mysql-connector-python 编写app.py文件 app.py文件如下&#xff1a; from flask import Flask, reque…

Docker逃逸---授权 SYS_ADMIN Capability逃逸原理浅析

目录 一、产生原因 二、利用条件 三、复现过程 1、容器内挂载宿主机cgroup 2、设置notify_no_release并寻找容器在宿主机上的存储路径 3、将恶意脚本写入release_agent 一、产生原因 给容器额外授权了SYS_ADMIN Cap&#xff0c;并且容器以root权限运行&#xff0c;攻击者…

HUAWEI(26)——防火墙双机热备

一、拓扑 二、需求 PC2 ping PC1 FW1与FW2双机热备,FW1为active,FW2为Standby,抢占延时1s VRRP 三、配置 1.IP地址,防火墙接口加入区域 防火墙用户名:admin 防火墙旧密码:Admin@123 防火墙新密码:admin@123 [FW1]interface GigabitEthernet 1/0/0 [FW1-GigabitEthe…

【计算机毕业设计】python在线课程培训学习考试系统637r7-PyCharm项目

使用说明 使用Navicat或者其它工具&#xff0c;在mysql中创建对应名称的数据库&#xff0c;并导入项目的sql文件&#xff1b; 使用PyCharm 导入项目&#xff0c;修改配置&#xff0c;运行项目&#xff1b; 将项目中config.ini配置文件中的数据库配置改为自己的配置&#xff0c;…

CSS 滚动驱动动画 animation-range

animation-range 语法 normallength-percentagetimeline-range-name 具名时间线范围 named timeline rangecovercontainentry 和 entry-crossingexit 和 exit-crossing 兼容性 animation-range 这个属性可同时对 scroll progress timeline 和 view progress timeline 这两种不…

机器学习笔记 - 使用3D卷积神经网络进行视频分类

1、导入相应的库 3D CNN 使用三维滤波器来执行卷积。内核能够在三个方向上滑动,而在 2D CNN 中它可以在二维上滑动。 首先安装并导入必要的库,用于处理ZIP文件内容的Remotezip 、用于使用进度条的tqdm 、用于处理视频文件的OpenCV 、用于执行更复杂的张量操作的einop…

计算机的总线

文章目录 前言一、总线的概述1.总线的概述&#xff08;是什么、什么用&#xff09;2.总线的分类2.1 片内总线2.2 系统总线2.2.1 数据总线2.2.2 地址总线2.2.3 控制总线 二、总线的仲裁1.为什么需要总线仲裁2.总线仲裁的方法2.1 链式查询2.2 计时器定时查询2.3 独立请求 总结 前…

激发创意,打造震撼视觉效果——Adobe After Effects 2024(Ae2024)全新来袭!

想要创造独特的、令人惊叹的视觉效果吗&#xff1f;不要犹豫&#xff0c;现在就升级到全新的Adobe After Effects 2024&#xff08;Ae2024&#xff09;&#xff01;作为业界领先的动态图形和视觉效果软件&#xff0c;Ae2024将为您的创作带来前所未有的火花。 Ae2024拥有强大的…

Android 音频可视化

Android音频可视化&#xff0c;指的是将音频的频率绘制到屏幕上&#xff0c;达到一种视觉效果&#xff0c;使播放或录制过程更加生动形象。 在Android进行视频可视化涉及的三个主要知识点,其中比较难以理解的傅里叶变换公式。 Android原生的Visualizer使用&#xff08;获取频…

someip 入门

什么是someip&#xff1f; SomeIP&#xff08;Scalable Service-Oriented MiddlewarE over IP&#xff09;是一种基于以太网的通信协议&#xff0c;用于汽车领域的通信。它允许不同的汽车电子控制单元&#xff08;ECUs&#xff09;之间通过网络进行通信&#xff0c;以便在车辆内…

网站的搭建与应用|企业APP软件定制开发|小程序

网站的搭建与应用|企业APP软件定制开发|小程序 网站是一种数字化媒体&#xff0c;它可以将我们的信息传递给全球的用户&#xff0c;让更多的人了解我们、了解我们的产品和服务。那么&#xff0c;如何搭建一个网站呢&#xff1f;下面&#xff0c;我将为大家介绍一下网站的建设步…

JavaScript 通过数组对JSON key字段进行排序

这里我以vue为例 不过json排序用的js方式 任何前端项目都可以通过js完成 我们组件代码现在是这样的 <template><div><div v-for "item in navCateList" :key "item.id">{{ item.name }}</div></div> </template>&…

【电源专题】电源芯片手册中的NVDC(narrow voltage DC)功能和电池充电曲线详解

在查看一些充电芯片的规格书时,会发现有一个NVDC功能。其中NVDC的全称是narrow voltage DC ,直译过来是窄电压DC电源架构。此外在规格书里还会发现NVDC Power Path Management字样,也就浊NVDC电源路径管理。 那么什么是NVDC电源路径管理? 如下所示当VIN有输入时(如适配器U…

【LeetCode】剑指 Offer Ⅱ 第7章:队列(6道题) -- Java Version

题库链接&#xff1a;https://leetcode.cn/problem-list/e8X3pBZi/ 类型题目解决方案滑动窗口剑指 Offer II 041. 滑动窗口的平均值队列&#xff1a;滑动窗口 ⭐剑指 Offer II 042. 最近请求次数队列&#xff1a;滑动窗口 ⭐二叉树宽搜剑指 Offer II 043. 在完全二叉树中添加节…

【软件设计师-从小白到大牛】上午题基础篇:第七章 程序设计语言与语言处理程序基础

文章目录 前言章节提要一、编译过程真题链接解释器与编译器特点与区别真题链接 二、文法的定义以及语法推导树真题链接 三、有限自动机与正规式四、表达式真题链接 五、函数调用&#xff08;传值与传址&#xff09;传值调用与传址调用真题链接 六、各种程序语言的特点真题链接 …

ubuntu下yolov7 tensorrt模型部署

文章目录 ubuntu下yolov7 tensorrt模型部署一、Ubuntu18.04环境配置1.1 安装工具链和opencv1.2 安装Nvidia相关库1.2.1 安装Nvidia显卡驱动1.2.2 安装 cuda11.31.2.3 安装 cudnn8.21.2.4 下载 tensorrt8.4.2.41.2.5 下载仓库TensorRT-Alpha并设置 二、从yolov7源码中导出onnx文…

【招招制敌】修改element-ui中el-image 预览图大小的默认尺寸,让展示效果更加有呼吸感

【招招制敌】修改element-ui中el-image 预览图大小的默认尺寸&#xff0c;让展示效果更加有呼吸感 1、问题&#xff08;需求&#xff09;2、解决2.1 深度修改不起效2.2 修改全局样式 3、效果 1、问题&#xff08;需求&#xff09; 在未修改前&#xff0c;el-image 预览图大小的…