C++高性能并行编程与优化 - 课件 - 01 学 C++ 从 CMake 学起
库就是受到他启发(完全是头文件组成) 6. fmtlib/fmt - 格式化库,提供 std::format 的替代品(需要 -DFMT_HEADER_ONLY ) 7. gabime/spdlog - 能适配控制台,安卓等多后端的日志库(和 fmt 冲突!) • 只需要把他们的 include 目录或头文件下载下来,然后 include_directories(spdlog/include) 即 可。 • 缺点:函数直 为例)的源码放到你工程的根目录: • 这些库能够很好地支持作为子模块引入: 1. fmtlib/fmt - 格式化库,提供 std::format 的替代品 2. gabime/spdlog - 能适配控制台,安卓等多后端的日志库 3. ericniebler/range-v3 - C++20 ranges 库就是受到他启发 4. g-truc/glm - 模仿 GLSL 语法的数学矢量 / 矩阵库 5. abseil/abseil-cpp0 码力 | 32 页 | 11.40 MB | 1 年前3C++高性能并行编程与优化 - 课件 - 性能优化之无分支编程 Branchless Programming
无分支编程 Branchless Programming by 彭于斌( @archibate ) 两种代码写法:分支 vs 三目运算符 两种使用方式:排序 vs 不排序 测试结果(均为 gcc -O3 ) 测试结果可视化 图表比较:分支 vs 无分支 分支 无分支 0 0.01 0.02 0.03 耗时(越低越好) 乱序 有序 • 传统的分支方法实现的 uppercase ,对于 烫伤了 去医院 10 烫伤了 多次训练 现代 CPU 流水线如何应付跳转指令:分支预测 • 假如有分支 A 和分支 B ,一开始 CPU 不确定会执行哪一条,会两条都预先执行(只计算 出中间结果,先不写回内存),等到了跳转指令(烧开水)处确定了要走分支 A 以后,就 把分支 A 的操作落到实处(写回内存),再把流水线中关于分支 B 的所有指令和数据删了 (浪费了 50% 的算力)。这就是说 的分支预测,根据历史的分支记录总结经验,不断调整两个分 支预执行的比例。其实就像训练神经网络一样,一直喂给他正确的数据,他就越来越自信。 • 随着 CPU 预判分支 A 成功的次数越来越多, CPU 对自己的结果就越来越自信,并进一 步加大预执行分支 A 所占的比例,从最初的 50% 到 60% 、 90% 、 99% 直到有一次, 突然出现了一次分支 B 成功的案例, CPU 瞬间被打脸!不得不浪费 99%0 码力 | 47 页 | 8.45 MB | 1 年前3C++高性能并行编程与优化 - 课件 - 10 从稀疏数据结构到量化数据类型
会把最高位复制 n 次。 因为补码的特性,这导致负数 >> 的结果仍是负 数。 这样就实现了和 Python 一样的始终向下取整除 法。 >> 2 = unsigned 类型的位运算 >> 不一样 而 unsigned 类型的 >> n 会不会复制最高位, 只是单纯的位移,这会导致负数的符号位单独被位 移,补码失效,造成结果不对。 unsigned 类型的 >> 会生成 shr 指令, 指令, signed 类型的 >> 会生成 sar 指令。 我们需要负方向无限延伸的稀疏数据结果,那就只 要 signed 那个就行。 >> 2 = 没有重合时可以用高效的加法:位运算 | • 如果可以保证 a 和 b 满足 a & b = 0 , 如: • 1011000 和 0000110 • 则: a + b = a | b | = 位运算 << :快速计算 2 的幂次方 自定义的稀疏数据结构: • hash().pointer(11).dense(8) 开源的体素处理库: OpenVDB • OpenVDB 的稀疏体积,可以存储符号距 离场 (SDF) ,也可以存储烟雾仿真的结果 等。 • 据张心欣说, OpenVDB 赢得了奥斯卡奖 。 • 因为他经常用在影视特效中,主要是符号 距离场有时比 mesh 处理起来方便很多。 OpenVDB 的设计:如果用 SNode0 码力 | 102 页 | 9.50 MB | 1 年前3C++高性能并行编程与优化 - 课件 - 06 TBB 开启的并行编程之旅
分钟。 • 他把图像切成 4 份,每个是原来的 1/4 大小 ,这样每个小块渲染只需要 1 分钟。 • 然后他把 4 个小块发给 4 个处理器核心, 1 分钟后 4 个处理器都渲染完毕得到结果。 • 最后只需将 4 个小块拼接起来即可得到完整 的 cornell box 图像。总共只花了 1 分钟。 图形学爱好者:我看中的是多核,目的是加速比,如果是单核,那多线程对我无用! 某互联 O(logn) ,工作复杂度为 O(n) 。 封装好了: parallel_reduce 保证每次运行结果一致: parallel_deterministic_reduce 并行缩并的额外好处:能避免浮点误差,例如求平均值 扫描( scan ) 如图所示,扫描和缩并差不多,只不过他会把求和的中间结果存到数组里去 1 个线程,依次处理 8 个元素的扫描,花了 7 秒 用电量: 1*7=7 度电 总用时: benchmark • 手动计算时间差有点太硬核了,而且只运 行一次的结果可能不准确,最好是多次运 行取平均值才行。 • 因此可以利用谷歌提供的这个框架。 • 只需将你要测试的代码放在他的 • for (auto _: bm) • 里面即可。他会自动决定要重复多少次, 保证结果是准确的,同时不浪费太多时间 。 运行结果 刚才的 BENCHMARK_MAIN 自动生成了一个 main 函数0 码力 | 116 页 | 15.85 MB | 1 年前3C++高性能并行编程与优化 - 课件 - 08 CUDA 开启的 GPU 编程
试图解决:通过指针传递 • 那你可能会想,既然不能返回,那作为指 针传入局部变量的引用,不就好了。 • 这样,在 cudaDeviceSynchronize() 以后 ,应该可以获取数据了吧? • 结果令人失望,尽管给 kernel 传了指向 ret 的指针,但 ret 的值并没有被改写成 功。 分析返回的错误代码 • CUDA 的函数,如 cudaDeviceSynchronize() 堆上分配试试? • 那你可能会想,难道是因为我的 ret 创建 在栈上,所以 GPU 不能访问,才出错的 ? • 于是你试图用 malloc 在堆上分配一个 int 来给 GPU 访问,结果还是失败了。 原因: GPU 使用独立的显存,不能访问 CPU 内存 • 原来, GPU 和 CPU 各自使用着独立的内 存。 CPU 的内存称为主机内存 (host) 。 GPU 使 用的内存称为设备内存 注意,这里一定要把 TOCK 放到同步之 后。原因之前说过,因为对 GPU 核函数 的调用是异步的,只有 cudaDeviceSynchronize() 以后才真正完 成执行,才能算出真的时间。 • 查看结果,发现 GPU 比 CPU 快了很多 ,这是当然的。 调整参数 • 适当调整板块数量 gridDim 和每板块的线 程数量 blockDim ,还可以更快一些。 • 顺便一提,这样的数学函数还有0 码力 | 142 页 | 13.52 MB | 1 年前3C++高性能并行编程与优化 - 课件 - 11 现代 CMake 进阶指南
>_SOURCE_DIR 等变量 小技巧: CMake 的 ${} 表达式可以嵌套 因为 ${PROJECT_NAME} 求值的结果是 hellocmake 所以 ${${PROJECT_NAME}_VERSION} 相当于 ${hellocmake_VERSION} 进一步求值的结果也就是刚刚指定的 0.2.3 了。 cmake_minimum_required 指定最低所需的 CMake 版本 假如你写的 第 7 章:变量与缓存 重复执行 cmake -B build 会有什么区别? 可以看到第二次的输出少了很多,这是因为 CMake 第一遍需要检测编译器 和 C++ 特性等比较耗时,检测完会把结果存储到缓存中,这样第二遍运行 cmake -B build 时就可以直接用缓存的值,就不需要再检测一遍了。 如何清除缓存?删 build 大法了解一下 然而有时候外部的情况有所更新,这时候 CMake cmake 错误可以用删 build 解决”“删 build 大法好”。 清除缓存,其实只需删除 build/CMakeCache.txt 就可以了 删 build 虽然彻底,也会导致编译的中间结果( .o 文件)都没了,重新编译要花费很长时间。 如果只想清除缓存,不想从头重新编译,可以只删除 build/CMakeCache.txt 这个文件。 这文件里面装的就是缓存的变量,删了他就可以让 CMake0 码力 | 166 页 | 6.54 MB | 1 年前3C++高性能并行编程与优化 - 课件 - 12 从计算机组成原理看 C 语言指针
int 实验:自动类型提升( type promotion ) • 如果两边有一边是 unsigned 的但是大小相等,则结果是 unsigned 的。 • unsigned int + int = unsigned int • 只有两边都是有符号的 int 时,结果才是有符号的 int 。 浮点与标准库数学函数 浮点数的二进制表示 • float 由 4 个字节组成,也就是 32 个位。 可以用 abs 这个函数取出一个整数的绝对值 。 abs 函数:取出绝对值 • 如果用来获取 float 类型的绝对值呢? • 编译通过了,但是结果却不对! • 你会发现 x 无论如何变化,都是 0.0 。 • 这其实是两个 bug 共同作用的结果。 printf 的错误:不会编译时检测参数类型是否正确 • 第一个 bug 是, printf 其实不知道他的参数是什 么类型,他只看到你字符串里写的 0x78 : 实验:你的电脑是大端还是小端? • 因此,我们只需做一下这个实验,就能检 测出当前电脑的架构是大端还是小端。 • 这里我们用 (char*)p 来强制转换指针类型 。 • 结果显示第一个房间里的字节是 0x78 。 • 可见我们的 x86 架构是小端字节序。 指针的用途举例:用于函数的多个返回值 • 我们知道函数只能有一个返回值,如果需 要返回多个变量,可以用指针作为参数。0 码力 | 128 页 | 2.95 MB | 1 年前3C++高性能并行编程与优化 - 课件 - 04 从汇编角度看编译器优化
存储在栈上无法动态扩充大小,这就是 为什么 vector 这种数据结构要存在堆上 ,而固定长度的 array 可以存在栈上 那么刚才那个例子改成 array 是不是就可 以自动优化成功了?你可以自己试试看, 想一想,为什么会是这个结果,然后在作 业的 PR 描述中和老师分享你的思考 那改用 array 试试? 那改用手写的 reduce ? 那改小到 10 ?成功了! 结论:代码过于复杂,涉及的语句数量 过多时,编译器会放弃优化! static 因为 static 声明表示不会暴露 other 给其 他文件,而且 func 也已经内联了 other , 所以编译器干脆不定义 other 了。 inline 关键字?不需要! 编译的结果完全一致? 结论:在现代编译器的高强度优化下,加不加 inline 无所谓 编译器不是傻子,只要他看得见 other 的函数体定义,就会自动内联 内联与否和 inline 没关系,内联与否只取决于是否在同文件,且函数体够小 有所谓的“老师”就不肯动动手敲几行命令(写 doc 文件倒挺勤的),在那里传播假知识。 • 在线做编译器实验推荐这个网站: https://godbolt.org/ • 可以实时看源代码编译的结果,还能选不同的编译器版本和 flag 。 • 不要脑内模拟!你误以为某更改对性能有帮助,然而实际测一下时间有一定可能反而变慢 。 第 3 章:指针 编译器傻了吗? 为什么编译器不优化掉 *c0 码力 | 108 页 | 9.47 MB | 1 年前3夏歌-使用Rust构建LLM应用
Prompt "0.1.0" 基于 ChatGPT 的 Telegram 机器人 当收到消息的时候,就按照预 设的 system_prompt 使用 GPT3.5 调用 OpenAI , 并把结果返回。 "0.1.0" 基于 ChatGPT 的 Telegram 机器人 在 ocr 这个 function 里 识别 图片中的文字, text_detection 是把识别的图片转化为 trigger 1. 汇总所有的总结 "0.1.0" 使用 Rust 构建 PR review 机器人 函数的 trigger 1. 规定了 review 结果返回的格式 2. 并把结果返回给程序 把 review 结果以 comment 的形式返 回到 GitHub https://github.com/flows-network/review-any- pr-with-chatgpt0 码力 | 36 页 | 38.31 MB | 1 年前3C++高性能并行编程与优化 - 课件 - 09 CUDA C++ 流体仿真实战
编译器,链接 OpenVDB 在 Blender 中查看导出的结果 边界条件 边界条件:初始化 边界条件:添加判断边界的版本 边界条件:仅在第一层额外判断边界条件 进一步改进 VDB 导出:支持导出多个网格,并指定名称 进一步改进 VDB 导出: P-IMPL 模式 进一步改进 VDB 导出: F-IMPL 模式 Blender 渲染结果 改进 改进边界条件:外部边界流出而不是反弹,内部边界可以流出速度 decayRate ,还和周围环境温度求平均值 改进温度:高温气体往上浮(作为外力来看待) 结果:更像火焰了 改进颜色场:让 clr 作为尘埃密度,密度越高越有向下坠落的趋势 问题:上面的尘埃无止境的飘下来 解决:纹理对象指定为 cudaAddressModeBorder 让越界访问自动变 0 即可 结果:小球加回来 改进温度:只有达到一定温度才会上升,否则(视为冷空气)下降 改进褪色:尘埃密度也会褪色0 码力 | 58 页 | 14.90 MB | 1 年前3
共 20 条
- 1
- 2