(done) 并行计算 CS149 Lecture2 (现代多核处理器) (SIMD, 多核, 超标量, 数据预取, 超线程)

视频 url: https://www.bilibili.com/video/BV1du17YfE5G?spm_id_from=333.788.videopod.sections&vd_source=7a1a0bc74158c6993c7355c5490fc600&p=2

大佬笔记 url: https://zhuanlan.zhihu.com/p/8129089606


先看视频:

Lecture 0 ~ 28min 的内容基本就是 cache,本科体系结构学过的东西。

sinx例子:串行版本

一个例子贯穿全课(使用泰勒展开计算 sinx):
该程序使用泰勒展开实现 sinx() 函数。为每一个 x (x数组中的每一个元素) 计算一个 y (y 数组中的每一个元素)。这是一个串行程序

void sinx(int N, int terms, float* x, float* y)
{for (int i = 0; i < N; i++){float value = x[i];float numer = x[i] * x[i] * x[i];int denom = 6; // 3!int sign = -1;for (int j = 1; j < terms; j++){value += sign * numer / denom;numer *= x[i] * x[i];denom *= (2 * j + 2) * (2 * j + 3);sign *= -1;}y[i] = value;}
}

sinx例子:C++ thread 双线程版本

上面这个程序可以通过改变程序的方式来实现并行,比如,我们可以通过 C++ thread,增加一个线程帮助我们实现并行:(下面这个代码应该是有 bug 的,但是无伤大雅)
但下面的代码有个问题:如果我的硬件有4个 CPU cores,那么下面的代码只能利用到我的两个 CPU cores,而非四个。

#include <thread>void sinx(int N, int terms, float* x, float* y)
{for (int i = 0; i < N; i++){float value = x[i];float numer = x[i] * x[i] * x[i];int denom = 6; // 3!int sign = -1;for (int j = 1; j < terms; j++){value += sign * numer / denom;numer *= x[i] * x[i];denom *= (2 * j + 2) * (2 * j + 3);sign *= -1;}y[i] = value;}
}typedef struct {int N;int terms;float* x;float* y;
} my_args;void my_thread_func(my_args* args)
{sinx(args->N, args->terms, args->x, args->y); // do work
}void parallel_sinx(int N, int terms, float* x, float* y)
{std::thread my_thread;my_args args;args.N = N/2;args.terms = terms;args.x = x;args.y = y;my_thread = std::thread(my_thread_func, &args); // launch threadsinx(N - args.N, terms, x + args.N, y + args.N); // do work on main threadmy_thread.join(); // wait for thread to complete
}

sinx例子:高级语言循环并行化版本

事实上,现代高级编程语言几乎都有这么一种抽象语义,来表示一个循环的每次迭代都是相互独立的,比如 C+OpenMP, pyTorch 等等,如下:
高级编程语言会根据硬件 cores 的数量,自动创建合适数量的线程去并行执行下面的代码。也就是说,如果我有 16 个 CPU cores,那么通常高级编程语言会帮助我把计算任务平均地分配给 16 个 CPU cores。
在这里插入图片描述

sinx例子:SIMD 版本

这个课堂给出的 SIMD 例子需要硬件和编译器的支持。

硬件上,要求一个 core 里有多个 ALU 元件,如下:
在这里插入图片描述

使用一个 ALU 的程序叫做 scalar program (标量程序)。

如下,使用 AVX 指令的代码则叫做 vector program (矢量程序)

#include <immintrin.h>void sinx(int N, int terms, float* x, float* y)
{float three_fact = 6; // 3!for (int i = 0; i < N; i += 8){__m256 origx = _mm256_load_ps(&x[i]);__m256 value = origx;__m256 numer = _mm256_mul_ps(origx, _mm256_mul_ps(origx, origx));__m256 denom = _mm256_broadcast_ss(&three_fact);int sign = -1;for (int j = 1; j < terms; j++){// value += sign * numer / denom__m256 tmp = _mm256_div_ps(_mm256_mul_ps(_mm256_set1_ps(sign), numer), denom);value = _mm256_add_ps(value, tmp);numer = _mm256_mul_ps(numer, _mm256_mul_ps(origx, origx));denom = _mm256_mul_ps(denom, _mm256_set1_ps((2*j+2) * (2*j+3)));sign *= -1;}_mm256_store_ps(&y[i], value);}
}

上面的 SIMD 源码会被编译器编译成下面的 SIMD 汇编指令,一个指令处理 256-bit 数据,从而加速
在这里插入图片描述

一个问题是,为什么选择在单核中增加 ALU,而不是直接增加核数?
回答:一个 ALU 相比一个完整的 CPU core 便宜很多

SIMD遇到分支判断结构怎么办?(使用掩码滤除)

如下图,当 SIMD 代码遇到分支判断结构时,可能无法同时执行 SIMD 指令。因为每次循环执行的指令流并不一致。(这种情况也叫做 “线程分化”)
此时有一种很直接的做法:让 CPU core 使用 SIMD 指令同时执行 if-else 中的所有代码,随后根据 if 判断的结果掩盖掉部分计算结果。
在这里插入图片描述

一个具体的例子如下:
源码:

// 伪代码:每个线程处理一个元素
if (data[i] > 5) {data[i] *= 2;  // 分支导致线程分化
}

生成掩码:

mask = (data > 5)  # 例如:mask = [1, 0, 1, ...]

应用掩码:

// 伪代码:所有线程执行相同指令,但仅掩码为1的通道生效
data = data * (2 * mask + (1 - mask))  // 满足条件时乘2,否则乘1(即不变)

更具体的例子:
Intel AVX512 指令集的掩码操作

// 示例:Intel AVX512 指令集的掩码操作
__mmask16 mask = _mm512_cmp_ps_mask(vec, threshold, _CMP_GT_OS);
result = _mm512_mask_mul_ps(vec, mask, vec, factor); // 仅掩码为1的通道执行乘法

CUDA 中使用掩码选择活跃线程

// 示例:CUDA 中使用掩码选择活跃线程
unsigned int mask = __ballot_sync(0xFFFFFFFF, data[i] > 5);
if (threadIdx.x % 32 < __popc(mask)) {// 仅满足条件的线程执行后续操作
}

在课堂例子中,极端情况下只有 1/8 的效率。
比如,只有线程1执行 if-True 的情况,其它7个线程执行 if-False的情况。
而 if-True 包含 expensive 的代码,if-False 包含 cheap 的代码。
那么,哪怕使用了上述掩码技术,效率仍然接近 1/8
在这里插入图片描述

指令流一致性和发散执行

指令流一致性:多个计算单元执行的指令序列是一致的
对于SIMD并行来说,指令流一致性是必要的
但对于多核并行来说,指令流一致性不必要,因为每个 core 有自己的 IFU 和 IDU。
发散执行:指的是一个程序中缺少 “指令流一致性”
在这里插入图片描述

SIMD 需要 CPU硬件支持、编译器支持、以及程序员的参与
在这里插入图片描述

三种不同的并行形式

1.超标量:例如 nutshell 的顺序双发、以及BOOM的乱序多发处理器,这种并行由CPU自己执行,没有编译器和程序员的参与。
2.SIMD:利用一个 CPU core 上多个 ALU,需要硬件、编译器和程序员的支持。
3.多核:在多个 CPU core 上运行同一个程序的多个线程。需要硬件、编译器和程序员的支持。
在这里插入图片描述

下图很好地介绍了三种并行的区别
在这里插入图片描述

这些并行形式可以混合,如下:
在这里插入图片描述在这里插入图片描述

数据预取(缓解内存IO开销)

内存访问仍然是一个大的性能瓶颈,除了缓存外,还有数据预取,如下
在这里插入图片描述

超线程技术(缓解IO开销)

还有一种解决内存瓶颈的方式:在 CPU core 里实现多个 execution context。(也叫超线程技术)
当遇到内存读取 cache miss 时,CPU 知道自己要等待很久,于是切换到第二个 thread 的 execution context 去执行第二个 thread
在这里插入图片描述

需要注意的是,上述方法提高了 overall system throughput,但实际上降低了单个线程的执行速度。(因为单个线程在内存数据读取完毕后,CPU core 并没有立刻切换回来执行)
在这里插入图片描述


再来看大佬笔记补充

3 实现SIMD的AVX2案例代码

SIMD这一思想对应的指令集就是我们常常在硬件评测类视频或文章中提到的AVXxxx系列指令集。AVX2是目前使用最广泛的SIMD指令集。AVX512支持的SIMD指令集的宽度更大, 但是目前还没有普及, 而且因为发热过大还在Intel最近几代CPU被移除了, 这里的案例代码也是基于AVX2的, 其支持的SIMD指令宽度为256位。

查看个人PC是否支持AVX2指令集的方法:

cat /proc/cpuinfo | grep avx2

3.1 中常用的函数总结

3.1.1 浮点运算函数

在这里插入图片描述
注意事项: 1. 函数名中的 ps 表示 packed single (单精度浮点) 2. 函数名中的 pd 表示 packed double (双精度浮点) 3. 256 表示使用 256 位寄存器 4. 单精度运算一次处理 8 个数 (256/32=8) 5. 双精度运算一次处理 4 个数 (256/64=4)


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

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

相关文章

Leetcode 3508. Implement Router

Leetcode 3508. Implement Router 1. 解题思路2. 代码实现 题目链接&#xff1a;3508. Implement Router 1. 解题思路 这一题就是按照题意写作一下对应的函数即可。 我们需要注意的是&#xff0c;这里&#xff0c;定义的类当中需要包含以下一些内容&#xff1a; 一个所有i…

Linux: 系统内核中的信号

目录 一 前言 二 信号在内核中的表示 三 sigset_t 四 信号集操作 1. sigpending() 2. sigemptyset() 3. sigfillset() 4. sigaddset ()和sigdelset() 5. sigismember() 6. sigprocmask() 五 深入理解信号的捕捉流程 一 前言 在Linux: 进程信号初识-CSDN博客信…

Nginx-keepalived-高可用

Nginx 高可用 通常 借助 Keepalived 实现&#xff0c; Keepalived 能通过 VRRP &#xff08;虚拟路由冗余协议&#xff09;让多个 Nginx 服务器 组成一个 热备集群&#xff0c;当主服务器故障时自动切换到备用服务器&#xff0c;保障服务不间断。 一、环境准备 角色IP 地址主…

使用python完成手写数字识别

入门图像识别的第一个案例,看到好多小伙伴分享,也把自己当初的思路捋捋,写成一篇博客,作为记录和分享,也欢迎各位交流讨论。 实现思路 数据集:MNIST(包含60,000个训练样本和10,000个测试样本) 深度学习框架:Keras(基于TensorFlow) 模型架构:卷积神经网络(CNN) 实…

Java学习总结-多线程-三种创建方法

什么是线程&#xff1f; 线程&#xff08;Thread&#xff09;是程序内部的一条执行流程。 程序如果只有一条执行流程&#xff0c;那这个程序就是单线程程序。 什么是多线程&#xff1f; 多线程是指从软硬件上实现的多条执行流程的技术&#xff08;多条线程由CPU负责调度执行…

电动垂直起降飞行器(eVTOL)

电动垂直起降飞行器&#xff08;eVTOL&#xff09;的详细介绍&#xff0c;涵盖定义、技术路径、应用场景、市场前景及政策支持等核心内容&#xff1a; 一、定义与核心特性 eVTOL&#xff08;Electric Vertical Take-off and Landing&#xff09;即电动垂直起降飞行器&#xf…

ensp 网络模拟器 思科华为基于VLANIF的公司网络搭建

该文章仅记录作业配置过程 如有雷同纯属巧合 一. 其它&#xff08;共1题&#xff0c;100分&#xff09; 1. (其它) 为大学生公司创建部门VLAN 1.项目 背景 为大学生公司现有财务部、技术部和业务部&#xff0c;出于数据安全的考虑&#xff0c;各部门的计算机需进行隔离。公…

使用`sklearn`中的逻辑回归模型进行股票的情感分析,以及按日期统计积极和消极评论数量的功能

以下是完成上述任务的Python代码&#xff0c;可在Jupyter Notebook中运行。此代码包含了使用sklearn中的逻辑回归模型进行情感分析&#xff0c;以及按日期统计积极和消极评论数量的功能。 import pandas as pd from sklearn.feature_extraction.text import TfidfVectorizer f…

oracle批量删除分区

为了清理数据&#xff0c;往往需要删除一些分区 简单查看当前分区 附件 --创建测试表 -- drop table test_part purge;CREATE TABLE test_part (sales_id NUMBER,sale_date DATE,amount NUMBER ) PARTITION BY RANGE (sale_date) INTERVAL (INTERVAL 1 MONTH) -- 每个月创建…

java流程控制08:For循环

For循环 虽然所有循环结构都可以用while或者do…while表示&#xff0c;但Java提供了另一种语句-----for循环&#xff0c;使一些循环结构变得更加简单。 for循环语句是支持迭代的一种通用结构&#xff0c;是最有效、最灵活的循环结构。 for循环执行的次数是在执行前就确定的。…

嵌入式软件开发调试方法

文章目录 1. 利于函数返回值,retrurn 定位错误位置2. 合理使用逻辑分析仪&#xff08;正点原子 厉害&#xff01;&#xff01;&#xff09; 1. 利于函数返回值,retrurn 定位错误位置 如下图所示&#xff0c;设置不同的返回值&#xff0c;0是ok的&#xff0c;其他值均为失败&…

P1025 [NOIP 2001 提高组] 数的划分(DFS)

题目描述 将整数 n 分成 k 份&#xff0c;且每份不能为空&#xff0c;任意两个方案不相同&#xff08;不考虑顺序&#xff09;。 例如&#xff1a;n7&#xff0c;k3&#xff0c;下面三种分法被认为是相同的。 1,1,5; 1,5,1; 5,1,1. 问有多少种不同的分法。 输入格式 n,k …

设计模式简述(三)工厂模式

工厂模式 描述简单工厂&#xff08;静态工厂&#xff09;工厂方法模式 抽象工厂增加工厂管理类使用 描述 工厂模式用以封装复杂的实例初始化过程&#xff0c;供外部统一调用 简单工厂&#xff08;静态工厂&#xff09; 如果对象创建逻辑简单且一致&#xff0c;可以使用简单工…

批量将 JSON 转换为 Excel/思维导入等其它格式

json 格式相信对大家来说都不陌生&#xff0c;这是一种轻量级的结构化数据&#xff0c;可以对对象进行描述。json 格式也是一种普通的文本文件格式&#xff0c;用记事本就能够打开编辑 json 格式的文件&#xff0c;可以很方便的转换为其他格式。今天要给大家介绍的就是如何将 j…

电脑有时出现检测不到音箱设备怎么办?

问题 有时候电脑开机之后就检测不到音箱&#xff0c;经过我一顿检查发现是检测不到声卡&#xff0c;即使拔插了音箱也没用&#xff0c;但是当我重启或者休眠之后再重启发现就检测到了 解决方案 方案一 重启或者休眠之后再开启 方案二 使用powershell指令将声卡弹出和载入…

Qwen-Agent框架的文件相关操作:从Assistant到BasicDocQA

在前面的几篇文章如《针对Qwen-Agent框架的Function Call及ReAct的源码阅读与解析&#xff1a;Agent基类篇》 、《基于Qwen-Agent框架的Function Call及ReAct方式调用自定义工具》、 《针对Qwen-Agent框架的源码阅读与解析&#xff1a;FnCallAgent与ReActChat篇》中&#xff0c…

RSSI定位程序,N个锚点、三维空间,使用CKF对轨迹进行滤波,附MATLAB代码的下载链接

本文所述的程序实现三维空间中基于RSSI信号的多锚点定位&#xff0c;并采用容积卡尔曼滤波&#xff08;CKF&#xff09;对动态轨迹进行降噪优化。代码包含完整的定位仿真流程&#xff0c;涵盖环境建模、信号强度模拟、定位解算、轨迹滤波及可视化分析模块 文章目录 程序介绍概述…

开源软件与自由软件:一场理念与实践的交锋

在科技的世界里&#xff0c;“开源软件”和“自由软件”这两个词几乎无人不知。很多人或许都听说过&#xff0c;它们的代码是公开的&#xff0c;可以供所有人查看、修改和使用。然而&#xff0c;若要细究它们之间的区别&#xff0c;恐怕不少朋友会觉得云里雾里。今天&#xff0…

C++ - 头文件基础(常用标准库头文件、自定义头文件、头文件引入方式、防止头文件重复包含机制)

一、头文件 在 C 中&#xff0c;头文件&#xff08;.h&#xff09;用于函数声明、类定义、宏定义等等 在 Visual Studio 中&#xff0c;头文件通常放在头文件目录中&#xff0c;头文件实现通常放在源文件目录中 二、常用标准库头文件 1、输入输出 <iostream> 标准输入…

CSS 背景属性学习笔记

一、CSS 背景属性概述 CSS 背景属性用于定义 HTML 元素的背景效果&#xff0c;主要包括以下几种属性&#xff1a; background-color&#xff1a;定义元素的背景颜色。 background-image&#xff1a;定义元素的背景图像。 background-repeat&#xff1a;定义背景图像如何重复…