C++高性能并行编程与优化 - 课件 - 07 深入浅出访存优化可惜这货只支持 int 做参数,要用 float 还得转换 一下指针类型, bitcast 一下参数。 stream 的特点:不会读到缓存里 • 因为 _mm_stream_si32 会绕开缓存,直 接把数据写到内存,之后读取的话,反而 需要等待 stream 写回执行完成,然后重 新读取到缓存,反而更低效。 • 因此,仅当这些情况: 1. 该数组只有写入,之前完全没有读取过 。 2 也不会越界。 ndarray :解决访问越界问题 • 我们采用了“索性分配更大数组”的办法。 • 因此我们现在给 ndarray 的模板加了一个 额外参数,用来控制边界层的大小。 • 这里我们的图像模糊操作需要向外扩张访 问 8 个元素,因此需要把 a 的边界层大小 声明为 8 ,即 ndarray<2, float, 8> 。 ndarray :解决起始地址对齐问题 有些 SIMD 指令要求地址对齐到一定字节数,否则会 structured grid )表示,那就是一个插桩操作。 • 插桩的内核( kernel )指的就是这个“周围范围”的形状(如右图三个例子) 和每个地方读取到值对修改自身值的权重等信息。 • 个人认为,图像处理中的模糊操作,或者是滤波操作,就属于插桩。有的 插桩内核各轴向是对称的(比如高斯模糊),有的是单单往一个方向延伸 很长(比如径向模糊),有的内核是正方形(箱滤波)。 • 人工智障圈子里好像管这个叫卷积(0 码力 | 147 页 | 18.88 MB | 1 年前3
C++高性能并行编程与优化 - 课件 - 06 TBB 开启的并行编程之旅是性能毕竟不是线性增长。 • 为什么无法做到呢?首先,为了保证缓存一致性以及其他握手协议需要运行时间开销。在 今天,双核或者四核机器在多线程应用方面,其性能不见得的是单核机器的两倍或者四倍。 这一问题一直伴随 CPU 发展至今。 并发和并行的区别 • 运用多线程的方式和动机,一般分为两种。 • 并发:单核处理器,操作系统通过时间片调 度算法,轮换着执行着不同的线程,看起来 就好像是同时运行一样,其实每一时刻只有 器的电脑,他正在渲染 cornell box 的图像, 这个图像在单核上渲染需要 4 分钟。 • 他把图像切成 4 份,每个是原来的 1/4 大小 ,这样每个小块渲染只需要 1 分钟。 • 然后他把 4 个小块发给 4 个处理器核心, 1 分钟后 4 个处理器都渲染完毕得到结果。 • 最后只需将 4 个小块拼接起来即可得到完整 的 cornell box 图像。总共只花了 1 分钟。 图形学爱 1 2 3 4 1 分 15 秒 1 分 30 秒 0 分 45 秒 0 分 30 秒 解决 1 :线程数量超过 CPU 核心数量,让系统调度保证各个核心始终饱和 • 因此,最好不是按照图像大小均匀等分,而是按照工 作量大小均匀等分。然而工作量大小我们没办法提前 知道……怎么办? • 最简单的办法:只需要让线程数量超过 CPU 核心数量 ,这时操作系统会自动启用时间片轮换调度,轮流执0 码力 | 116 页 | 15.85 MB | 1 年前3
C++高性能并行编程与优化 - 课件 - 08 CUDA 开启的 GPU 编程dim3(n, 1, 1)>>> 的简写而已。 图片解释三维的板块和线程 • 之所以会把 blockDim 和 gridDim 分三维主要是因为 GPU 的业务常常涉及到三维图形学和二维图像,觉得 这样很方便,并不一定 GPU 硬件上是三维这样排列 的。 • 三维情况下同样可以获取总的线程编号(扁平化)。 • 如需总的线程数量: blockDim * gridDim • 如需总的线程编号: SM 之间调度你提交的 各个板块。正如操作系统在多个 CPU 核心之间调度线程那样…… • 不过有一点不同, GPU 不会像 CPU 那样做时间片轮换——板块一旦被调度到了一个 SM 上,就会一直执行,直到他执行完退出,这样的好处是不存在保存和切换上下文(寄 存器,共享内存等)的开销,毕竟 GPU 的数据量比较大,禁不起这样切换来切换去…… • 一个 SM 可同时运行多个板块,这时多个板块共用同一块共享内存(每块分到的就少了) 效,可以把数组故意搞成不对齐的 33 跨步来避免。 • 顺便一提,英伟达的 warp 大小是 32 ,而 AMD 的显卡则是 64 ,其他概念如共享内存基本类似 。 第 10 章:插桩操作实战 读写图像 • 首先是读写图像的函数,利用了 stb_image 这个单头文件库。 X 方向模糊 • 然后看实现径向模糊的核函数。 Y 方向模糊 • Y 方向同理。 经典案例: jacobi 迭代 • 相比第七课0 码力 | 142 页 | 13.52 MB | 1 年前3
C++高性能并行编程与优化 - 课件 - 12 从计算机组成原理看 C 语言指针这里我们是在 func 内部调用了 malloc ,当然我们需要告诉调用者去 free 我们返回的指 针。 • size_t* n 用于额外返回数组的大小,如果不需要也可以不加。 • 例子:读取图像像素值作为数组的 stbi_load 函数(稍后要 stbi_free 释放他返回的指 针)。 函数需要输入或输出数组?分类讨论 • 第三种情况有一个更好的解决方案,那就是分成两个函数: • 问到被其他变量覆盖的数据。 解决:使用 malloc 或者 new 在堆上分配数组 • 可以改用 malloc 或者 new 在堆上分配 内存,堆上内存是持久的,只要你不手动 free 他就一直在那里。 • 这样函数退出时不会释放数组,调用者就 可以访问到正确的数据了。 • 当然这样需要调用者在退出时手动调用一 下 free(a) ,因为堆内存不会自动释放。 太复杂了?没关系,用 C++0 码力 | 128 页 | 2.95 MB | 1 年前3
C++高性能并行编程与优化 - 课件 - 01 学 C++ 从 CMake 学起标准库的功能,难免会用到一些第三方库。 • 最友好的一类库莫过于纯头文件库了,这里是一些好用的 header-only 库: 1. nothings/stb - 大名鼎鼎的 stb_image 系列,涵盖图像,声音,字体等,只需单头文件! 2. Neargye/magic_enum - 枚举类型的反射,如枚举转字符串等(实现方式很巧妙) 3. g-truc/glm - 模仿 GLSL 语法的数学矢量0 码力 | 32 页 | 11.40 MB | 1 年前3
C++高性能并行编程与优化 - 课件 - 性能优化之无分支编程 Branchless Programming分钟 全身 无条件跳转指令 • 还有一个小问题,就是执行正常的分支走到“拉 粑粑”后,还会去医院。 • 为了在正常分支里不去医院,我们在“拉粑粑”后 面加一条无条件跳转指令,不论条件如何,直 接跳转到去医院的下一条指令,也就是程序结 束。 • if (! 烫伤 ) { • 刷牙 ; 看比站 ; 吃饭 ; 拉粑粑 ; • } else { • 去医院 ; • } 任务 如果预判失败,最后走了分支 B ,那就不得不把预先执行分支 A 的数据全部删了,浪费 90% 的算力。这就是 CPU 的分支预测,根据历史的分支记录总结经验,不断调整两个分 支预执行的比例。其实就像训练神经网络一样,一直喂给他正确的数据,他就越来越自信。 • 随着 CPU 预判分支 A 成功的次数越来越多, CPU 对自己的结果就越来越自信,并进一 步加大预执行分支 A 所占的比例,从最初的 50% 到 60% 、0 码力 | 47 页 | 8.45 MB | 1 年前3
C++高性能并行编程与优化 - 课件 - 15 C++ 系列课:字符与字符串,则抛出 out_of_range 异常。 • 如果 pos + len ≥ s.size() ,则截断超出的部分,只返回小于 len 的子字符串 。 • 如果不指定 len 则默认为 -1 ,这时会一直从 pos 开始到字符串结尾。 • s.find(str, pos = 0) : • 如果找不到子字符串 str 则返回 -1 (也就是 string::npos )。 • 如果 pos ≥ s.size() 。 • 美国人虽然有点不高兴,但毕竟 2 字节总比 4 字节好,而且也有 助于他的产品“ Windows” 畅销世界各地呀,所以最终 Windows 系 统内部就一致采用了 UTF-16 格式一直沿用至今。 • 当然, Windows 为了更好地伺候中国客户,还专门把中文 Windows 系统的默认编码格式改成了 GBK ,大大妨碍了程序员编 程和国际交流的便利性,可以说是“比尔盖茨,我卸卸你哦”了。0 码力 | 162 页 | 40.20 MB | 1 年前3
C++高性能并行编程与优化 - 课件 - 05 C++11 开始的多线程编程// OK ,能保证原子性 • counter++; // OK ,能保证原子性 fetch_add :和 += 等价 • 除了用方便的运算符重载之外,还可以直 接调用相应的函数名,比如: • fetch_add 对应于 += • store 对应于 = • load 用于读取其中的 int 值 fetch_add :会返回其旧值 • int old0 码力 | 79 页 | 14.11 MB | 1 年前3
C++高性能并行编程与优化 - 课件 - 13 C++ STL 容器全解之 vector之后即使不直接使用外面的那个临时对象 a , 也可以继续通过 data() 指针来访问数据。 vector 容器:延续生命周期 • 也可以移动到一个全局变量的 vector 对象。 • 这样数组就会一直等到 main 退出了才释放。 • 小彭老师曾经在 taichi 中就是用了一个全局 变量伺候了 unique_ptr 脱离作用域会释放的 麻烦,让 lambda 中仍可访问对象。 • 至于那个全局变量本身有没有被使用则无所谓0 码力 | 90 页 | 4.93 MB | 1 年前3
共 9 条
- 1













