MIT 6.172 笔记 现代硬件算法案例分析

本文是https://en.algorithmica.org/hpc/和MIT 6.172的课后题解析
课程地址:

文章目录

    • HW2 Profiling Serial Merge Sort
      • 测试DEBUG和非DEBUG区别
      • 测试inline和非inline区别
      • Coarsening
    • HW3 向量化
      • 为什么用负偏移量
      • 测量向量化
      • 跨步向量化
    • HW4 Reducer Hyperobjects
      • 比较openmp和pthread
      • omp汇编指令分析
      • omp task实现
      • 优化fib
      • 优化transpose
      • schedule dynamic/static/runtime
    • Cilk运行时
    • HW 6 & HW7: Custom Memory Allocators
    • HW9 & HW10 lock-free和wait-free
      • x86的默认内存模型
      • 并行编程的golden和silver法则
      • 使用setarch x86_64 -R来关闭ASLR
      • 确定性的hashtable
    • 矩阵乘法分析
      • strict alias能起到对齐的作用吗

HW2 Profiling Serial Merge Sort

HW2的课程对应的是L2: Bentley’s Rules和L3:Bit Hacks
关于Bit Hacks可以看https://zhuanlan.zhihu.com/p/37014715

测试DEBUG和非DEBUG区别

HW2介绍了下perf和valgrind,不多展开。
首先课程要求我们用cachegrind分析sort在DEBUG和非DEBUG模式下的命中情况,并且解释指令计数代替时间的优点和缺点。
课程默认是用clang的,但是clang14和clang17经过我测试对cachegrnd的兼容性都不好,读镜像文件报错,需要打补丁:https://bugs.kde.org/show_bug.cgi?id=452758
太麻烦了,因此我换成了gcc
DEBUG模式:
image.png
非DEBUG模式:
image.png

测试inline和非inline区别

这里和我的直觉相违背,事实上:
编译器几乎不需要inline辅助内联,但是我们仍然可以用inline增加被内联的概率。
image.png
这里的性能差距主要是下面的跨翻译单元的alloc函数带来的:

void inline mem_alloc(data_t** space, int size) {*space = (data_t*) malloc(sizeof(data_t) * size);if (*space == NULL) {printf("out of memory...\n");}
}void inline mem_free(data_t** space) {free(*space);*space = 0;
}

我们看这里生成的汇编代码:
image.png
指针和数组的比较比较简单,在此跳过。

Coarsening

void sort_c(data_t* A, int p, int r) {assert(A);// In practice, merge sort is slow for small array sizes. As such, using// faster sorting techniques (i.e. insertion sort) when the array size is// small (aka < 100), can make a significant improvement in the runtime.if (r-p < 16) {isort(&(A[p]), &(A[r]));} else {int q = (p + r) / 2;sort_c(A, p, q);sort_c(A, q + 1, r);merge_c(A, p, q, r);}
}

这里Coarsening的粒度主要收到cacheline的影响,因为L1的miss率是0。
用64字节的cacheline可以算出最佳参数应该是16。
16
image.png
image.png
100
image.png
image.png
剩下的就是空间重用,很简单不多介绍了。

HW3 向量化

为什么用负偏移量

第一个问题很申必:
image.png
f4f5f3b7563c766851fbceae8be56809.png
这里没有memcpy那种overlap问题,为什么用负偏移量呢?
问了下别人说是因为loop latch可以省一个cmp,而且现在clang已经不会这样做了(有指令fusion所以意义不大了),因此我们不需要太在意这个问题。

测量向量化

没有向量化的情况:
image.png
4宽向量化
image.png
perf分析乘法:
耗时最多的是索引更新操作
image.png
perf分析加法:
耗时最多的是向量化的加法
image.png

跨步向量化

跨步不会进行向量化:
image.png
强制进行向量化试试:
image.png
并没有变快
我们看看跨步向量化的汇编代码
image.png
跨步向量化会多出一个vextracti128指令进行提取,但是耗时很低

HW4 Reducer Hyperobjects

比较openmp和pthread

https://en.wikipedia.org/wiki/OpenMP#Pros_and_cons
优点:
可移植的多线程代码(在C/C++和其他语言中,通常必须调用特定于平台的原语才能获得多线程)。
简单:不需要像MPI那样处理消息传递。
数据布局和分解由指令自动处理。
可扩展性与共享内存系统上的MPI相当。[39]
增量并行:可以一次处理程序的一个部分,不需要对代码进行显著的更改。
串行和并行应用程序的统一代码:当使用顺序编译器时,OpenMP构造被视为注释。
在使用OpenMP并行化时,通常不需要修改原始(串行)代码语句。这减少了无意中引入错误的机会。
粗粒度和细粒度并行都是可能的。
在不完全遵循SPMD计算模式的不规则多物理应用中,如在紧密耦合的流体颗粒系统中遇到的那样,OpenMP的灵活性可以比MPI具有很大的性能优势。
可用于各种加速器,如GPGPU[41]和FPGA。
缺点:
引入难以调试的同步错误和竞争条件的风险。
截至2017年,仅在共享内存多处理器平台上有效运行(但请参阅Wayback Machine和其他分布式共享内存平台上的英特尔集群OpenMP存档2018-11-16)。
需要支持OpenMP的编译器。
可扩展性受到内存体系结构的限制。
不支持比较和交换。
缺少可靠的错误处理。
缺乏细粒度机制来控制线程处理器映射。
意外编写错误共享代码的几率很高。

omp汇编指令分析

见https://github.com/Chang-LeHung/openmp-tutorial/blob/master/docs/for.mdhttps://github.com/Chang-LeHung/openmp-tutorial/blob/master/docs/for.md
omp关键的几个操作用到了
lock cmpxchg、cmpxchg和__sync_fetch_and_add
如果划分的块比较小,那就不会用失败率比较高的cmpxchg,而是用__sync_fetch_and_add

omp task实现

见https://github.com/Chang-LeHung/openmp-tutorial/blob/master/docs/task.md
image.png

优化fib

#include <stdio.h>
#include <inttypes.h>
#include <stdlib.h>#include "omp.h"int64_t fib(int64_t n) {if (n < 2) return n;int64_t x, y;if (n < 19) {x = fib(n - 1);y = fib(n - 2);}else {
#pragma omp task shared(x)x = fib(n - 1);
#pragma omp task shared(y)y = fib(n - 2);
#pragma omp taskwait}return (x + y);
}int main(int argc, char* argv[]) {int64_t n = atoi(argv[1]);int64_t result;omp_set_num_threads(4);int nthreads = omp_get_num_threads();printf("N = %d\n", nthreads);// result = fib(n);
#pragma omp parallel{
#pragma omp master{result = fib(n);}}printf("Fibonacci of %" PRId64 " is %" PRId64 ".\n", n, result);
}

优化transpose


void transpose(Matrix* arr) {uint16_t i, j;// Parallel section:
#ifdef _OPENMP
#pragma omp parallel for private(i, j) num_threads(8) schedule(dynamic)for (i = 1; i < arr->rows; i++) {for (j = 0; j < i; j++) {uint8_t tmp = arr->data[i][j];arr->data[i][j] = arr->data[j][i];arr->data[j][i] = tmp;}}goto end;
#endif// Serial section:for (i = 1; i < arr->rows; i++) {for (j = 0; j < i; j++) {uint8_t tmp = arr->data[i][j];arr->data[i][j] = arr->data[j][i];arr->data[j][i] = tmp;}}goto end;end:return;
}

schedule dynamic/static/runtime

https://stackoverflow.com/questions/10850155/whats-the-difference-between-static-and-dynamic-schedule-in-openmp
schedule(static,2) 每个线程分配两个循环块
比如:

#pragma omp parallel for schedule(static,2) num_threads(8)
for (i = 0; i < 8; i++)memset(&a[i*4096], 1, 4096);
|             | core 0 | thread 0 | a[0]     ... a[8191]  <- OK, same memory node
| socket 0    | core 1 | thread 1 | a[8192]  ... a[16383] <- OK, same memory node
| NUMA node 0 | core 2 | thread 2 | a[16384] ... a[24575] <- Not OK, remote memory
|             | core 3 | thread 3 | a[24576] ... a[32768] <- Not OK, remote memory|             | core 4 | thread 4 | <idle>
| socket 1    | core 5 | thread 5 | <idle>
| NUMA node 1 | core 6 | thread 6 | <idle>
|             | core 7 | thread 7 | <idle>

#pragma omp for schedule(dynamic,1)
用在每个循环块执行时间由较大差别的情况。**schedule(runtime) 是 OpenMP 中用于动态选择循环迭代分配方式的一种方式。具体含义如下:

  • runtime:表示在运行时动态选择循环迭代分配方式。这意味着循环迭代的分配方式将在程序运行时由运行时系统根据环境和系统的情况进行选择。

Cilk运行时

image.png

  1. RIP:RIP是英特尔x86处理器架构中的指令指针寄存器(Instruction Pointer Register)的缩写。该寄存器存储了当前正在执行的指令的内存地址。当处理器执行指令时,它会从RIP寄存器中获取下一条要执行的指令的地址。
  2. RBP:RBP是英特尔x86处理器架构中的基址指针寄存器(Base Pointer Register)的缩写。在函数调用过程中,RBP通常被用作栈帧的基址指针,用于定位函数的局部变量和参数。通过RBP,函数可以访问其局部变量和参数的内存位置。
  3. RSP:RSP是英特尔x86处理器架构中的栈指针寄存器(Stack Pointer Register)的缩写。栈是一种常用的数据结构,用于存储函数调用期间的局部变量、参数和返回地址等信息。RSP寄存器指向当前栈顶的内存地址,当新的数据被推入或弹出栈时,RSP的值会相应地更新。

HW 6 & HW7: Custom Memory Allocators

后面的两节都是选修课,因为有很多优秀的实现,比如mimalloc、tcmalloc,我之前也分析过,所以这节就跳过了,我们看下一节,lock free和wait free,这个还是很重要的。

HW9 & HW10 lock-free和wait-free

x86的默认内存模型

image.png

并行编程的golden和silver法则

golden rule:永远不要编写非确定性程序
silver rule:永远不要编写非确定性程序,如果必须要编写,那么使用完备的测试用例

使用setarch x86_64 -R来关闭ASLR

确定性的hashtable

e00dd9ba181b4aa04c87ba8ba2e7b1fa.png
这里的问题在于probe可能超出范围,所以我们通过控制线程数可以实现 good和bad
image.png

矩阵乘法分析

https://en.algorithmica.org/hpc/algorithms/matmul/

strict alias能起到对齐的作用吗

在https://en.algorithmica.org/hpc/algorithms/matmul/中说__restrict__能解决alias问题,获得性能提升,但实际上:
https://quick-bench.com/q/GxzfeO3Jmul8g0dw9K1oKgQ_9fw
image.png
可以看得出来,对齐比alias在矩阵乘中作用大得多。

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

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

相关文章

[Qt的学习日常]--信号和槽

前言 作者&#xff1a;小蜗牛向前冲 名言&#xff1a;我可以接受失败&#xff0c;但我不能接受放弃 如果觉的博主的文章还不错的话&#xff0c;还请点赞&#xff0c;收藏&#xff0c;关注&#x1f440;支持博主。如果发现有问题的地方欢迎❀大家在评论区指正 本期学习&#xff…

【JAVA】一文掌握Java并发编程

Java 开发中&#xff0c;并发编程属于相当重要的一个知识点&#xff0c;可以说&#xff0c;Java 的并发能力&#xff0c;是成就今日 Java 地位的因素之一。Java 的并发编程由浅入深实质上是包含 Java&#xff08;API&#xff09;层、JVM&#xff08;虚拟机&#xff09;层、内核…

[Linux][网络][网络编程套接字][一][预备知识][套接字地址结构]详细讲解

目录 0.预备知识1.理解源IP地址和目的IP地址2.理解源MAC地址和目的MAC地址3.端口号4.理解端口号和进程ID5.理解源端口号和目的端口号6.通过IP地址、端口号、协议号进行通信识别7.认识TCP协议和UDP协议8.网络字节序 1.套接字地址结构(sockaddr) 0.预备知识 1.理解源IP地址和目的…

redisson分布式锁的单机版应用

package com.redis;/*** author linn* date 2024年04月23日 15:31*/ import org.redisson.Redisson; import org.redisson.api.RedissonClient; import org.redisson.config.Config; import org.springframework.context.annotation.Bean; import org.springframework.context.…

从 0 到 1 创建、测试并发布属于自己的 Go 开源库

作者&#xff1a;陈明勇 个人网站&#xff1a;https://chenmingyong.cn 文章持续更新&#xff0c;如果本文能让您有所收获&#xff0c;欢迎点赞收藏加关注本号。 微信阅读可搜《程序员陈明勇》。 这篇文章已被收录于 GitHub https://github.com/chenmingyong0423/blog&#xff…

AIGC——什么是人工智能生成内容

人工智能生成内容&#xff08;AIGC&#xff09;是当今数字时代的一个引人注目的前沿技术&#xff0c;它借助深度学习和自然语言处理等技术&#xff0c;使计算机系统具备了生成高质量文本、图像、音频等多媒体内容的能力。AIGC的出现不仅推动了信息技术的发展&#xff0c;也在多…

判断字符串由几个单词组成(C语言)

一、N-S流程图&#xff1b; 二、运行结果&#xff1b; 三、源代码&#xff1b; # define _CRT_SECURE_NO_WARNINGS # include <stdio.h>int main() {//初始化变量值&#xff1b;int world 0;int i 0;char c 0;char string[81] { 0 };int num 0;//提示用户&#xff…

C++---重载

1、运算符重载 #include <iostream> using namespace std; class complex { int rel; int vir; public: complex(){} complex(int rel,int vir):rel(rel),vir(vir){} void show() { cout << rel << "" << vir << "i" <&l…

(待更)DRF: 序列化器、View、APIView、GenericAPIView、Mixin、ViewSet、ModelViewSet的源码解析

前言&#xff1a;还没有整理&#xff0c;后续有时间再整理&#xff0c;目前只是个人思路&#xff0c;文章较乱。 注意路径匹配的“/” 我们的url里面加了“/”&#xff0c;但是用apifox等非浏览器的工具发起请求时没有加“/”&#xff0c;而且还不是get请求&#xff0c;那么这…

31.Gateway网关-跨域问题

跨域 1.域名不同&#xff1a;www.baidu.com和www.taobao.com,www.taobao.org 2.域名相同&#xff0c;端口不同。localhost:8080和localhost:8081 跨域问题 浏览器禁止请求的发起者与服务端发生跨域ajax请求&#xff0c;请求被浏览器拦截的问题。 解决方案 CORS 浏览器询…

0426_C高级4

练习1&#xff1a; 输入一个数字&#xff0c;实现数字逆置&#xff08;不使用字符串截取方式&#xff09; 1 #!/bin/bash2 read -p "输入一个数字&#xff1a;" number3 p$number4 result5 while [ $p -ne 0 ]6 do7 result$((result*10p%10))8 p$((p/10))9 …

input框添加验证(如只允许输入数字)中文输入导致显示问题的解决方案

文章目录 input框添加验证(如只允许输入数字)中文输入导致显示问题的解决方案问题描述解决办法 onCompositionStart与onCompositionEnd input框添加验证(如只允许输入数字)中文输入导致显示问题的解决方案 问题描述 测试环境&#xff1a;react antd input (react的事件与原生…

无人机GB42590接收端 +接收端模组,同时支持2.4G与5.8G双频

严格按照GB42590的协议开发的发射端&#xff0c;通过串口和模块通讯&#xff0c;默认波特率 921600。 http://www.doit.am/深圳四博智联科技有限公司https://shenzhendoit.taobao.com/category-1734422372.htm?spma1z10.1-c-s.0.0.560c74d77eT01G&searchy&catNameGB4…

VC2022 + protobuf

google这是有私心啊&#xff0c;protobuf从某个版本开始&#xff0c;依赖了一个google自己推出的大型组件集&#xff0c;Abseil&#xff0c;有点类似于Boost了&#xff0c;业内用的人&#xff0c;从个人狭窄的圈子来说&#xff0c;应该是不多的&#xff0c;据说google的众贤用的…

【LLMOps】小白详细教程,在Dify中创建并使用自定义工具

文章目录 博客详细讲解视频点击查看高清脑图 1. 搭建天气查询http服务1.1. flask代码1.2. 接口优化方法 2. 生成openapi json schema2.1. 测试接口2.2. 生成openapi schema 3. 在dify中创建自定义工具3.1. 导入schema3.2. 设置工具认证信息3.3. 测试工具 4. 调用工具4.1. Agent…

docker启动的mysql8中文乱码问题和无法输入中文

问题描述&#xff1a; 1.中文显示乱码 2.无法输入中文 中文乱码临时方案&#xff1a; show variables like ‘character%’; SET NAMES utf8mb4; SET CHARACTER SET utf8mb4; 中文乱码永久方案&#xff1a; vim /etc/my.cnf[client] default-character-setutf8mb4[mysql]…

Linux详解:进程创建

文章目录 进程创建fork函数写时拷贝页表fork常规用法fork调用失败的原因 进程创建 fork函数 在linux 中fork函数&#xff0c;它从已经存在的进程中创建一个新的进程&#xff0c;新进程为子进程&#xff0c;而原进程为父进程。 #include<unistd.h> pid_t fork(void);返…

嵌入式学习58-ARM7(字符设备驱动框架led)

知识零碎&#xff1a; kernel 内核 printk 内核打印 cat /proc/devices insmod …

如何通过安全数据传输平台,保护核心数据的安全传输?

在数字化的浪潮中&#xff0c;企业的数据安全传输显得尤为关键。随着网络攻击手段的日益复杂&#xff0c;传统的数据传输方式已不再安全&#xff0c;这就需要我们重视并采取有效的措施&#xff0c;通过安全数据传输平台来保护核心数据。 传统的数据传输面临的主要问题包括&…

【介绍下IDM的实用功能】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…