C++高性能并行编程与优化 -  课件 - 07 深入浅出访存优化虽说连续、顺序访问是最理想的,然而在使用哈希表等数据结构中,不 可避免的会通过哈希函数得到随机的地址来访问,且 Value 类型可能小 于 64 字节,浪费部分带宽。怎么办? 解决:按 64 字节分块地随机访问 • 解决方法就是,把数据按 64 字节大小分块。随 机访问时,只随机块的位置,而块的内部仍然按 顺序访问。 • 可以看到 64 字节分块的效果拔群,但还是比顺 序访问慢一些,为什么?明明没有浪费带宽了? 小。 • 加速比: 16 倍,大概已经到极限了? 进亿步优化 • 将两个方法一起用上,并用 stream_ps 防止 写回操作污染缓存。 • 最终加速比: 24 倍。这里可以看到 i - 2 和 i + 2 跨步的访存似乎不是很理想,可能还能 进亿步优化,出于时间原因就没继续深入, 同学们可以课后研究一下。 第 5 章:内存分配与分页 vector :写入两次,时间都是一样的(理所当然) 分配比机器大得多的内存(比如 2048*2028*1024 的三维网格),然后 在里面索引,这样就相当于利用硬件的分页机制实现了稀疏数据结构,既能高效利用内存 ,随机访问和插桩又特别高效。有兴趣可以研究一下他们的论文,也用了莫顿序增强 TLB 和缓存的局域性,非常精彩。 vector 也可以不初始化:只需使用一个帮手类 也可以使用小彭老师封装好的帮手类 重复分配效率低 • 即使第二次分配0 码力 | 147 页 | 18.88 MB | 1 年前3
 C++高性能并行编程与优化 -  课件 - 05 C++11 开始的多线程编程• lambda 的函数体将在另一个线程里执行 。 • 接下来你可以在 main 里面做一些别的事 情, download 会持续在后台悄悄运行。 • 最后调用 future 的 get() 方法,如果此时 download 还没完成,会等待 download 完成,并获取 download 的返回值。 显示地等待: wait() • 除了 get() 会等待线程执行完毕 外, :失败,陷入等待 • 双方都在等着对方释放锁,但是因为等待而无法 释放锁,从而要无限制等下去。 • 这种现象称为死锁( dead-lock )。 解决 1 :永远不要同时持有两个锁 • 最为简单的方法,就是一个线程永远不要 同时持有两个锁,分别上锁,这样也可以 避免死锁。 • 因此这里双方都在 mtx1.unlock() 之后才 mtx2.lock() ,从而也不会出现一方等着对 方的同时持有了对方等着的锁的情况。 。这样就可以在函数体退出 时自动调用 unlock_shared() ,更加安全 了。 • shared_lock 同样支持 defer_lock 做参数 , owns_lock() 判断等,同学们自己研究 。 只需一次性上锁,且符合 RAII 思想:访问者模式 Accessor 或者说 Viewer 模式,王鑫磊常用于设计 GPU 容器 OpenVDB 数据结构的访问,也是采用了 Accessor0 码力 | 79 页 | 14.11 MB | 1 年前3
 C++高性能并行编程与优化 -  课件 - 08 CUDA 开启的 GPU 编程语言的整数除法 n / nthreads ,他是向下取整的,比如 7 / 4 = 1 。 • 比如 n 为 65535 ,那么最后 127 个元 素是没有赋值的。 解决边角料难题 • 解决方法就是:采用向上取整的除法。 • 可是 C 语言好像没有向上整除的除法这 个运算符?没关系,用这个式子即可: • (n + nthreads - 1) / nthreads • 例如: (7 + ,也不会漏掉几个元素。 • 这样一个 for 循环非常符合 CPU 上常见的 parallel for 的习惯,又能自动匹配不同的 blockDim 和 gridDim ,看起来非常方便。 本方法出自英伟达官方博客: https://developer.nvidia.com/blog/cuda-pro-tip-write-flexible-kernels-grid-stride-loops/ 但是为什么这里用了 2^24 个元素,按理说应 该卡的不行了,却还是非常快的样子? • 那是因为 CUDA 编译器比较聪明,自动优化 了……稍后会解释他优化的原理。 解决:线程局部变量 • 解决方法之一就是:先累加到局部变量 local_sum ,最后一次性累加到全局的 sum 。 • 这样每个线程就只有一次原子操作,而不 是网格跨步循环的那么多次原子操作了。 当然,我们需要调小0 码力 | 142 页 | 13.52 MB | 1 年前3
 C++高性能并行编程与优化 -  课件 - 10 从稀疏数据结构到量化数据类型别的地方去。这时液体曾经存在过的地方 也仍然处于激活状态,可以每隔若干帧及 时释放掉这些不用的指针块以节省内存。 unordered_map 作为顶层,指针作为中层,稠密数组作为底层 • 实现稀疏的方法有: • hash 哈希(本例中的 unordered_map ) • pointer 指针(本例中的 Block1 ) • dense 稠密(本例中的 Block ) • 他们之间可以相互组合,形成更复杂的稀疏数据结构。 float 和 half 之间转换,然后用 int16_t 来存储。double: float: float16: 转换起来简单一点的: bfloat16 (大指数版) • 另一种简单的方法,就是直接暴力地把 32 位浮 点从 16 位切断,只取出高 16 位,当做一种非 标准的 half 来存储。称为 bfloat16 (前面多个 b )。 • 因为 bfloat16 是从 float 优点:平坦直观,适合插桩,顺序访问,自适应网格 。 • 缺点:尺寸受限,操作系统挂钩,依赖 x86 硬件机 制。 • 顺便一提, GPU 也可以搞 SPGrid ,不过 GPU 的 页大小是 2MB ,王鑫磊最近研究过这个,因为太繁 琐而放弃了。 http://pages.cs.wisc.edu/~sifakis/papers/SPGrid.pdf 今天的回家作业 • 用稀疏数据结构改良康威生命游戏 (conway’s0 码力 | 102 页 | 9.50 MB | 1 年前3
 C++高性能并行编程与优化 -  课件 - 13 C++ STL 容器全解之 vector在构造时就初始化其中元素的值。 • 例如创建具有 6, 1, 7, 4 四个元素的 vector : • vectora = {6, 1, 7, 4}; • 和刚刚先创建再赋值的方法相比更直观。 • vector(initializer_list list); vector 容器:构造函数 • 初始化表达式的等号可以写也可以不写: • vector a 区间;也可以是一个容器的部分,例如 {a.begin() + 1, a.end() - 1} 相当于去头去尾后的列表,相当于 Python 中的 a[1:-1] 。 • 区间这个概念在 C++20 被高度强化了亿下,之后的课我们研究一下他。 vector 容器: begin 和 end • begin 可以获取指向第一个元素所在位置的迭代 器。可以通过 *a.begin() 来访问第一个元素。 • 迭代器支持加法运算,例如 0 码力 | 90 页 | 4.93 MB | 1 年前3
 C++高性能并行编程与优化 -  课件 - 02 现代 C++ 入门:RAII 内存管理可变性 / 多线程等概念作为语言基本元素存在。这些在我们的 业务里面是非常重要的,所以不可替代。 • (试图升华文章中心主旨) 扩展阅读关键字 • 限于篇幅,此处放出一些扩展知识供学有余力的同学研究: 1. P-IMPL 模式 2. 虚函数与纯虚函数 3. 拷贝如何作为虚函数 4. std::unique_ptr::release() 5. std::enable_shared_from_this0 码力 | 96 页 | 16.28 MB | 1 年前3
 C++高性能并行编程与优化 -  课件 - 性能优化之无分支编程 Branchless Programming图表比较:分支 vs 无分支 分支 无分支 0 0.01 0.02 0.03 耗时(越低越好) 乱序 有序 • 传统的分支方法实现的 uppercase ,对于 排序过的数据明显比乱序时高效。 • 无分支的方法对于乱序和有序的数据一样 高效,性能吊打了传统的分支方法。 • 对于传统分支的做法,为什么排序了的更 高效?既然无分支更高效,我要怎样优化 才能让我的程序变成无分支的呢?那就来 大于(无符号) above e 等于 equal ne 不等于 not equal http://unixwiz.net/techtips/x86-jumps.html 手动进行无分支优化的方法 无分支优化:从汇编角度分析 • 发生了什么?让我们把源码和汇编逐个对应。 • x 是第一个参数(通过 edi 传入,被存入 rbp 指向的堆 栈) • 比较 x 和 0 的大小( cmp • 可以被优化成: • a + (cond) * (b - a) // 方法 1 • 或者更满足“对称强迫症”的: • (cond) * a + !(cond) * b // 方法 2 • 还有一种“摆烂”的做法: • (cond ? a : b) // 方法 3 • 三目运算符通常会变成和 if-else 一样的分 支,同样会生成条件跳转指令,理应一样0 码力 | 47 页 | 8.45 MB | 1 年前3
 C++高性能并行编程与优化 -  课件 - 17 由浅入深学习 map 容器at 。 • 那么他们两个又有什么区别呢?很多新手都分不清他俩,可能只认识 [] 。 读取 map 元素 • mapm; • 读取 map 中指定键值的元素有两种方法。 • val = m[“key”]; // 读取键值为 “ key” 的元素,如果不存在,那就创建 “ key” 元素 • val = m.at(“key”); 的读取这里,严重影 响他的调试效率( javascript 的 undefined 直呼内行)。 写入 map 元素 • map m; • 写入 map 中指定键值的元素有两种方法。 • m[“key”] = val; // 写入键值为 “ key” 的元素,如果不存在,那就创建 “ key” 元素 • m.at(“key”) = val; 的特性,由于调用者是 = val 赋值,所 以初始化也没用了,反正马上会写入 val 。 浅谈这种精分设计的原因 • 总结,要符合你熟悉的 Python 的 [] 行为,在 C++ 中要根据不同情况选择不同的方法访 问: • 读取用 at() 写入用 [] • 很多同学会困惑,为什么要设计两套, C++ 他爸是精神分裂症吗? • 恰恰相反, C++ 是中两个函数不论读写都一视同仁: at 0 码力 | 90 页 | 8.76 MB | 1 年前3
 C++高性能并行编程与优化 -  课件 - 16 现代 CMake 模块化项目管理指南和第三方库作者约定俗成的,由第三方库的安装程序 负责把包配置文件放到这里。如果第三方库的作者比较懒,没提供 CMake 支持(由安装 程序提供 XXXConfig.cmake ),那么得用另外的一套方法( FindXXX.cmake ),稍后细 谈。 Windows 系统下的搜索路径 •/ • /cmake/ • / */ • 1/msvc2017/lib/cmake/Qt5/Qt5Config.cmake ,那 么请你设置变量 Qt5_DIR 为 D:/Qt5.12.1/msvc2017/lib/cmake/Qt5 。有三种设置方法: • (1) 单次有效。在 configure 阶段,可以从命令行设置(注意要加引号): • cmake -B build -DQt5_DIR=”D:/Qt5.12.1/msvc2017/lib/cmake/Qt5” /opt/Qt5.12.1/lib/cmake/Qt5/Qt5Config.cmake ,那么请你设 置变量 Qt5_DIR 为 /opt/Qt5.12.1/lib/cmake/Qt5 。有三种设置方法: • (1) 单次有效。在 configure 阶段,可以从命令行设置: • cmake -B build -DQt5_DIR=”/opt/Qt5.12.1/lib/cmake/Qt5” • 0 码力 | 56 页 | 6.87 MB | 1 年前3
 Go读书会第二期包导入相关 • 代码块与作用域 • 控制语句的惯用法与坑 Part4 – 语法基础:函数与方法 践行哲学,遵循惯例,认清本质,理解原理 • Init 函数 • 成为“一等公民” • defer 的惯用法与坑 • 变长参数函数妙用 • 方法的本质、 receiver 参数类型选择、方法集 合 Go 程序逻辑的基本承载单元 Part5 – 语法核心:接口 践行哲学,遵循惯例,认清本质,理解原理 com/bigwhite/ GoProgrammingFromBeginnerToMaster 读书实践与体会 第三部分 Go 技术图书阅读:从外刊到内刊 Go 中文图书 Go 外文图书 读书方法 精读 • 选择高质量图书 • 脑图 + 细节摘录 + 行动清单(输出) 泛读 • 闲书 ( 不烧脑 ) • 碎片化(快读) + 听书 小结 第四部分 小结 • 写书三要素0 码力 | 26 页 | 4.55 MB | 1 年前3
共 23 条
- 1
 - 2
 - 3
 













