C++高性能并行编程与优化 - 课件 - 17 由浅入深学习 map 容器值坑了他。所以他们又另起炉灶,发明了越界时不会自动创建零值,而是能抛出异常的 at 函数。 C++ 和 Python 用法对比 C++ 和 Python 用法对比(运算符重载展开成普通函数后) 简单粗暴的 Java 用法 • 与 Python 和 C++ 不同, Java 放弃了花里胡哨的运算符重载,索性都采用成员函数 get put 来表示,非常明确。主要是为了把 get 和 put 作为接口函数,可以对应多个具体 这里说的指针,不光是 T * 指针,还包括 T & 引用, iterator 迭代器,他们都是指针的 变体。 • 而 structural-binding 和 range-based loop 语法支持引用,也非常简单: • for (auto &[k, v]: m) { • v = v2; // 引用比指针还方便,自动解引用。此处等价于迭代器版的 (*it).second = v2; • } map 这里说的指针,不光是 T * 指针,还包括 T & 引用, iterator 迭代器,他们都是指针的 变体。 • 而 structural-binding 和 range-based loop 语法支持引用,也非常简单: • for (auto &[k, v]: m) { • v = v2; // 引用比指针还方便,自动解引用。此处等价于迭代器版的 (*it).second = v2; • } map0 码力 | 90 页 | 8.76 MB | 1 年前3
C++高性能并行编程与优化 - 课件 - 06 TBB 开启的并行编程之旅似乎这里 reduce 的加速比是逻辑核心数量,而 for 的加速比是物理核心的数量? • 剧透:因为本例中 reduce 是内存密集型, for 是计算密集型。 • 超线程对 reduce 这种只用了简单的加法,瓶颈在内存的算法起了作用。 • 而本例中 for 部分用了 std::sin ,需要做大量数学运算,因此瓶颈在 ALU 。 • 这里卖个关子,欲知后事如何,请待下集揭晓! 更专业的性能测试框架: 核心在闲置着,因为任务简单已经算完了,只有 4 号核心一个人在处理额外的光线。 1 2 3 4 1 分 15 秒 1 分 30 秒 0 分 45 秒 0 分 30 秒 解决 1 :线程数量超过 CPU 核心数量,让系统调度保证各个核心始终饱和 • 因此,最好不是按照图像大小均匀等分,而是按照工 作量大小均匀等分。然而工作量大小我们没办法提前 知道……怎么办? • 最简单的办法:只需要让线程数量超过 的 sin(i) 值 并行筛选 1 (张心欣犯过的错) 利用多线程安全的 concurrent_vector 动态追加数据 基本没有加速,我猜想 concurrent_vector 内部可能 用了简单粗暴的互斥量,只保证了安全,并不保证高 效 加速比: 1.32 倍 并行筛选 2 先推到线程局部( thread-local )的 vector 最后一次性推入到 concurrent_vector0 码力 | 116 页 | 15.85 MB | 1 年前3
C++高性能并行编程与优化 - 课件 - 07 深入浅出访存优化并行能减轻计算瓶颈,但不减轻内存瓶颈,故后者是优化的重点 。 浮点加法的计算量 • 冷知识:并行地给浮点数组每个元素做一次加法反而更慢。 • 因为一次浮点加法的计算量和访存的超高延迟相比实在太少了。 • 计算太简单,数据量又大,并行只带来了多线程调度的额外开销 。 • 小彭老师经验公式: 1 次浮点读写 ≈ 8 次浮点加法 • 如果矢量化成功( SSE ): 1 次浮点读写 ≈ 32 次浮点加法 • 如果 的限制:最好是连续的写入 • 需要注意, stream 系列指令写入的地址 ,必须是连续的,中间不能有跨步,否则 无法合并写入,会产生有中间数据读的带 宽。 写入 1 比写入 0 更慢? • 很简单,因为写入 0 被编译器自动优化成 了 memset ,而 memset 内部利用了 stream 指令得以更快写入。 写入 1 比写入 0 更慢?解决 • 解决办法就是,我们也用 stream swap(a, b); // 交换双缓冲 • // 不断反复 ... • 但是这样每个循环体内只有 1 次加法,明显就是我 们所说的 mem-bound 嘛! 递推公式,一步抵两步 • 一种简单的思路是: • 既然递推公式 a’[i] = (a[i - 1] + a[i + 1]) * 0.5 • 那么也应该有 a’’[i] = (a’[i - 1] + a’[i + 1]) * 0.50 码力 | 147 页 | 18.88 MB | 1 年前3
谈谈MYSQL那点事技巧分享 Q Q & & AA MyISAM MyISAM 特点 特点 MyISAM vs MyISAM vs InnoDB InnoDB • 数据存储方式简单,使用 数据存储方式简单,使用 B+ Tree B+ Tree 进行索引 进行索引 • 使用三个文件定义一个表: 使用三个文件定义一个表: .MYI .MYD .frm .MYI .MYD plain-slow -log – – 德国工程师使用 德国工程师使用 Perl Perl 开发的把 开发的把 Slow Log Slow Log 输出到屏幕,功能简单 输出到屏幕,功能简单 mysql-log-filter - Google code - Google code 上一个开源产品,报表 上一个开源产品,报表 简洁 简洁0 码力 | 38 页 | 2.04 MB | 1 年前3
C++高性能并行编程与优化 - 课件 - 性能优化之无分支编程 Branchless Programming吃饭 看比站 拉粑粑 5 5 10 20 条件跳转指令 • 让不占用相同资源的任务同时进行,这也是 CPU 流水线的初衷。但理想是美好的,现实 是骨感的,对于程序来说,指令不只是一个 个简单的任务,有时候我们需要做判断,来 决定要执行的具体任务,这就是分支,在汇 编语言中体现为条件跳转指令。 • 例如我们这里给任务清单加一个,如果烧开 水时被烫伤,则直接去医院的特殊任务。 • return 1; 优化成 return !(x > 0); • 但是这里因为碰巧分支的两端都是简单的 1 和 0 ,对于其他值应该怎么办? 无分支优化套路:妙用加减乘 • if (x > 0) • return 42; • else • return 32; • 两边不是简单的 0 和 1 了,怎么办?其实可以利用加法和乘法: • return 32 + (x > 0) * setg 和 cmovbe )。 所以手动优化真的有必要吗? 所以手动优化真的有必要吗? • 可见只有 -O0 的时候手动优化才有优势,开 启 -O1 优化以后都区别不大了。 • 因此对于简单的分支,完全可以不考虑优化, 交给编译器自动优化掉。 • 一般只需要把 if-else 改成三目运算符 ?: 编 译器就能成功识别了(见开头的例子)。 • 建议只有当性能遇到瓶颈时,再去针对性对0 码力 | 47 页 | 8.45 MB | 1 年前3
C++高性能并行编程与优化 - 课件 - 10 从稀疏数据结构到量化数据类型,从而支持用 lambda 捕获的访问者模式。 实现访问者模式 • 额,总之就是每一层都有一个缓存。 第 5 章:量化整型 使用 int :每个占据 4 字节 • 记得我第七课说过,一个简单的循环体往 往会导致内存成为瓶颈( memory- bound )。 • 右边就是一个很好的例子。 使用 int64_t :每个占据 8 字节 • 如果用更大的数据类型,用时会直接提升两倍! • 因此需要我们手写一些位运算,在 float 和 half 之间转换,然后用 int16_t 来存储。double: float: float16: 转换起来简单一点的: bfloat16 (大指数版) • 另一种简单的方法,就是直接暴力地把 32 位浮 点从 16 位切断,只取出高 16 位,当做一种非 标准的 half 来存储。称为 bfloat16 (前面多个 b )。 • 因为 • float16 具有 5 位指数, 10 位底数。 • 可见 bfloat16 的指数部分占得比较多,而底数 就很少,这样会有一点不精确,优点是和 float 之间转换的位运算实现起来比较简单。 double: float: bfloat16: 图片解释 bfloat16 , float16 , float32 的关系 bfloat16 因为指数一样是 8 位,所以范围没有变。0 码力 | 102 页 | 9.50 MB | 1 年前3
C++高性能并行编程与优化 - 课件 - 04 从汇编角度看编译器优化第 1 章:化简 编译器优化:代数化简 编译器优化:常量折叠 编译器优化:举个例子 编译器优化:我毕竟不是万能的 结论:尽量避免代码复杂化,避免使用会造 成 new/delete 的容器。 简单的代码,比什么优化手段都强。 造成 new/delete 的容器:我是说,内存分配在堆上的容器 • 存储在堆上(妨碍优化): • vector, map, set, string, function 业的 PR 描述中和老师分享你的思考 那改用 array 试试? 那改用手写的 reduce ? 那改小到 10 ?成功了! 结论:代码过于复杂,涉及的语句数量 过多时,编译器会放弃优化! 简单的代码,比什么优化手段都强。 constexpr :强迫编译器在编译期求值 结论:如果发现编译器放弃了自动优化,可以 用 constexpr 函数迫使编译器进行常量折叠! 不过, constexpr 一次写入 4 个 int ,一次计算 4 个 int 的加法,从而更加高 效但这样有个缺点,那就是数组的大小必须为 4 的整数倍 否则就会写入越界的地址! 如果不是 4 的倍数?边界特判法 看不懂?很简单,假设 n = 1023 : 先对前 1020 个元素用 SIMD 指令填入,每次处理 4 个 剩下 3 个元素用传统的标量方式填入,每次处理 1 个 思想:对边界特殊处理,而对大部分数据能够矢量化0 码力 | 108 页 | 9.47 MB | 1 年前3
C++高性能并行编程与优化 - 课件 - 01 学 C++ 从 CMake 学起• 但坏处也很明显: 1. make 在 Unix 类系统上是通用的,但在 Windows 则不然。 2. 需要准确地指明每个项目之间的依赖关系,有头文件时特别头疼。 3. make 的语法非常简单,不像 shell 或 python 可以做很多判断等。 4. 不同的编译器有不同的 flag 规则,为 g++ 准备的参数可能对 MSVC 不适用。 构建系统的构建系统( CMake ) • ,他就能够在调用时生成当前系统所支持的构建系统。 • 需要准确地指明每个项目之间的依赖关系,有头文件时特别头疼。 • CMake 可以自动检测源文件和头文件之间的依赖关系,导出到 Makefile 里。 • make 的语法非常简单,不像 shell 或 python 可以做很多判断等。 • CMake 具有相对高级的语法,内置的函数能够处理 configure , install 等常见需求。 • 不同的编译器有不同的 flag • find_package(fmt REQUIRED) • target_link_libraries(myexec PUBLIC fmt::fmt) • 为什么是 fmt::fmt 而不是简单的 fmt ? • 现代 CMake 认为一个包 (package) 可以提供多个库,又称组件 (components) ,比如 TBB 这个包,就包含了 tbb, tbbmalloc, tbbmalloc_proxy0 码力 | 32 页 | 11.40 MB | 1 年前3
C++高性能并行编程与优化 - 课件 - 02 现代 C++ 入门:RAII 内存管理会被释放两次!更危险的则是 v1 被解构而 v2 仍在被使用的情况。 • 这就是为什么“如果一个类定义了解构函数,那么 您必须同时定义或删除拷贝构造函数和拷贝赋值函 数,否则出错。” 解决方案:要么删除 • 最简单的办法是,直接禁止用户拷贝这个 类的对象,在 C++11 中可以用 = delete 表示这个函数被删除,让编译器不要自动 生成一个默认的(会导致指针浅拷贝的) 拷贝构造函数了。 • 这样就可以在编译期提前发现错误: 限于篇幅,其实构造函数还完全没讲完…… 下一讲继续完善我们刚才发明的 Vector 类 ! 也会详解 && 到底有哪些意思…… 来学智能指针压压惊 • 如果构造函数全家桶搞得你晕头转向了,那让我们来点(相对)简单的作为饭后甜点吧! C++98 :令人头疼的内存管理 • 在没有智能指针的 C++ 中,我们只能手 动去 new 和 delete 指针。这非常容易出 错,一旦马虎的程序员忘记释放指针,就 中,有时候我们会遇到移交控 制权后,仍希望访问到对象的需求。 • 如果还是用 p 去访问的话,因为被移动构 造函数转移了, p 已经变成空指针,从而 出错。 解决方案:提前获取原始指针 • 最简单的办法是,在移交控制权给 func 前,提前通过 p.get() 获取原始指针: 解决方案:提前获取原始指针(续) • 不过你得保证 raw_p 的存在时间不超过 p 的生命周期,否则会出现危险的空悬指0 码力 | 96 页 | 16.28 MB | 1 年前3
C++高性能并行编程与优化 - 课件 - 11 现代 CMake 进阶指南是单核心的构建系统, Makefile 虽然多核心但因历史兼容原因效率一 般。 • 而 Ninja 则是专为性能优化的构建系统,他和 CMake 结合都是行业标准了。 Ninja 和 Makefile 简单的对比 性能上: Ninja > Makefile > MSBuild Makefile 启动时会把每个文件都检测一遍, 浪费很多时间。特别是有很多文件,但是实 际需要构建的只有一小部分,从而是 I/O 中指定的“最小所需版本号”。 注意: cmake_minimum_required 不仅是“最小所需版本” 虽然名字叫 minimum_required ,实际上不光是 >= 3.15 就不出错这么简单。 根据你指定的不同的版本号,还会决定接下来一系列 CMake 指令的行为。 此外,你还可以通过 3.15...3.20 来表示最高版本不超过 3.20 。 这会对 cmake_policy 有所影响,稍后再提。 时就可以直接用缓存的值,就不需要再检测一遍了。 如何清除缓存?删 build 大法了解一下 然而有时候外部的情况有所更新,这时候 CMake 里缓存的却是旧的值,会导致一系列问题。 这时我们需要清除缓存,最简单的办法就是删除 build 文件夹,然后重新运行 cmake -B build 。缓存是很多 CMake 出错的根源,因此如果出现诡异的错误,可以试试看删 build 全部 重新构建。 经典 CMake0 码力 | 166 页 | 6.54 MB | 1 年前3
共 21 条
- 1
- 2
- 3













