现代C++ 教程:高速上手C++11/14/17/20
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 2 目录 目录 外部模板 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 尖括号 态服务,进而大幅优化运行期的性能。因此模板也被很多人视作 C++ 的黑魔法之一。 外部模板 传统 C++ 中,模板只有在使用时才会被编译器实例化。换句话说,只要在每个编译单元(文件)中 编译的代码中遇到了被完整定义的模板,都会实例化。这就产生了重复实例化而导致的编译时间的增加。 并且,我们没有办法通知编译器不要触发模板的实例化。 为此,C++11 引入了外部模板,扩充了原来的强制编译器在特定位置实例化模板的语法,使我们能 -> 的形式进行(我们在上一节前面的尾返回类型已经提到过这种写法了)。 所谓捕获列表,其实可以理解为参数的一种类型,Lambda 表达式内部函数体在默认情况下是不能 够使用函数体外部的变量的,这时候捕获列表可以起到传递外部数据的作用。根据传递的行为,捕获列 表也分为以下几种: 1. 值捕获 与参数传值类似,值捕获的前提是变量可以拷贝,不同之处则在于,被捕获的变量在 Lambda 表达式被创建时拷贝,而非调用时才拷贝:0 码力 | 83 页 | 2.42 MB | 1 年前3C++高性能并行编程与优化 - 课件 - 15 C++ 系列课:字符与字符串
hex 选项。 • 但是他的输出会保存到一个字符串里。 • 调用成员函数 .str() 就能取出这个字符串了。 • 之后这个字符串就可以用作其他用途,比如 printf 打印,或者用于查询数据库,都没问题。 • 这里比较无聊,最后还是直接输出到了 cout 。 stringstream 也可以取代 stoi • 刚刚展示了 stringstream 模仿 cout 的方法。 • stringstream ≥ s.size() 时,会抛出 std::out_of_range 异常终止程序。使用 gdb 等调试 器就可以在出这个异常的时候暂停,帮你调试错误 ( BV1kP4y1K7Eo )。也可以从外部函数 catch 住这个异常(以 后再讲)。 • 而 [] 则不会抛出异常,他只是简单地给字符串的首地址指针和 i 做个加法运算,得到新的指针并解引用。如果你给的 i 超过了字符 串大小 i ≥0 码力 | 162 页 | 40.20 MB | 1 年前3《深入浅出MFC》2/e
本书光盘片内含书中所有的范例程序,包括源代码与EXE 档。中介文件(如.OBJ 和.RES 等)并未放入。所有程序都可以在Visual C++ 5.0 整合环境中制作出来。安装 方式很简单(根本没有什么安装方式):利用DOS 外部指令,XCOPY,把整个光盘片 拷贝到你的硬盘上即是了。 范例程序说明 ■ Generic(第1章):这是一个Win32 程序,主要用意在让大家了解Win32 程 式的基本架构。 ■ 等等,就得包含对应的头文件,例如 COMMDLG.H 或MAPI.H 或TAPI.H 等等。 7 以消息为基础,以事件驱动之(message based, event driven) Windows 程序的进行系依靠外部发生的事件来驱动。换句话说,程序不断等待(利用一 个while 回路),等待任何可能的输入,然后做判断,然后再做适当的处理。上述的「输 入」是由操作系统捕捉到之后,以消息形式(一种数据结构)进入程序之中。操作系统 Browser 提供这些【Funct i ons】项目供观察: Browser 提供这些【Dat a】项目供观察: Browser 系从一个特殊的数据库文件(.BSC)取得信息,此文件由Visual C++ 整合环境自 动产生,非常巨大。如果暂时你不想要这个数据库,可以把图4-5a 中的【Generate browse Info】选项清除掉。而当你需要它时,选择【Tools/Source Browse0 码力 | 1009 页 | 11.08 MB | 1 年前3C++高性能并行编程与优化 - 课件 - 08 CUDA 开启的 GPU 编程
blockDim ,看起来非常方便。 从线程到板块 • 核函数内部,用之前说到的 blockDim.x + blockIdx.x + threadIdx.x 来获取线程在整个 网格中编号。 • 外部调用者,则是根据不同的 n 决定板块的 数量( gridDim ),而每个板块具有的线程 数量( blockDim )则是固定的 128 。 • 因此,我们可以用 n / 128 作为 gridDim CUDA 源码生效,这样可以混合其他 .cpp 文件也不会发生 gcc 报错的情况了。。 如何捕获外部变量? • 如果试图用 [&] 捕获变量是会出错的,毕 竟这时候捕获到的是堆栈( CPU 内存)上 的变量 arr 本身,而不是 arr 所指向的内 存地址( GPU 内存)。 如何捕获外部变量? • 你可能会想,是不是可以用 [=] 按值捕获 ,这样捕获到的就是指针了吧? • 错了,不要忘了我们第二课说过, 错了,不要忘了我们第二课说过, vector 的拷贝是深拷贝(绝大多数 C++ 类都是深 拷贝,除了智能指针和原始指针)。这样 只会把 vector 整个地拷贝到 GPU 上! 而不是浅拷贝其起始地址指针。 如何捕获外部变量? • 正确的做法是先获取 arr.data() 的值到 arr_data 变量,然后用 [=] 按值捕获 arr_data ,函数体里面也通过 arr_data 来访问 arr 。0 码力 | 142 页 | 13.52 MB | 1 年前3C++高性能并行编程与优化 - 课件 - 04 从汇编角度看编译器优化
map, set, string 等 …… constexpr :强迫编译器在编译期求值(续) 发现:会让编译变得很慢,因为这 50000 次迭代是在编译期进行的。 第 2 章:内联 调用外部函数: call 指令 @PLT 是 Procedure Linkage Table 的缩 写,即函数链接表。链接器会查找其他 .o 文件中是否定义了 _Z5otheri 这个符号, 如果定义了则把这个 优化失败 因为编译器看不到那个文件的 other 函数里是什么,哪怕 other 在定义他的文件里是个空函数,他也不敢优化掉。 解决方案:放在同一个文件里 结论:避免在 for 循环体里调用外部函数,把他们移到 同一个文件里,或者放在头文件声明为 static 函数。 将 other 放到 和 func 同一个 .cpp 文件里,这样编译器看得到 other 的函 数体,就可以内联化该函数 0 的局部变量,再累加到 c 也能矢量化成功!该解决方案比起前一种,由于加法顺 序原因,算出来的浮点精度更高。 优化手法总结 1. 函数尽量写在同一个文件内 2. 避免在 for 循环内调用外部函数 3. 非 const 指针加上 __restrict 修饰 4. 试着用 SOA 取代 AOS 5. 对齐到 16 或 64 字节 6. 简单的代码,不要复杂化 7. 试试看 #pragma0 码力 | 108 页 | 9.47 MB | 1 年前3C++高性能并行编程与优化 - 课件 - 07 深入浅出访存优化
数据。如果没有,则从内存中读取,并存储到缓存中; 如果有,则直接使用缓存中的数据。 • 这样一来,访问的数据量比较小时,就可以自动预先加 载到这个更高效的缓存里,然后再开始做运算,从而避 免从外部内存读写的超高延迟。 缓存的分级结构 查看高速缓存大小: lscpu • 可以看到我们 x86 电脑的缓存结构分为三级。 • 一级缓存分为数据缓存和指令缓存,其中数据缓存有 32 KB , 数组就完全不会被读取,不会占用内 存带宽,从而带宽利用率是 100% ,因此比 AOS 快了 2 倍。 AOSOA :两者得兼 • 还有一种办法就是让 MyClass 内部是 SOA ,而外部仍是一个 vector的 AOS—— 这种内存布局称为 AOSOA 。 • 缺点是必须保证数量是 1024 的整数倍, 而且因为要两次指标索引,随机访问比较 烦。 次跳跃,每次跳跃的距离是 nx ,从而 缓存容量需要有 nx*nblur 那么大,才能利用 全部的缓存,而小彭老师的一级缓存只有 32KB 大。 • 因此可以用循环分块( loop tiling ),将外部 两层循环变为 blockSize 为跨步的,而内部 则在区间 [xBase, xBase + blockSize) 上循环 。 循环分块:用图片来直观感受 BM_y_blur BM_y_blur_tiled 0 码力 | 147 页 | 18.88 MB | 1 年前3C++高性能并行编程与优化 - 课件 - Zeno 中的现代 C++ 最佳实践
return zzz; }() • 可以在表达式层面里插入一个语句块,本 质上是立即求值的 lambda 表达式(内部 是分号级别,外部是逗号级别)。 • 在函数体内也可以这样: • [&]{ xxx; yyy; return zzz; }() • 来在语句块内使用外部的局部变量。 带有构造函数和解构函数的类 • 实际上,只需定义一个带有构造函数和解构函 数的类(这里的 Helper ),然后一个声明该类0 码力 | 54 页 | 3.94 MB | 1 年前3C++高性能并行编程与优化 - 课件 - 11 现代 CMake 进阶指南
C++ 特性等比较耗时,检测完会把结果存储到缓存中,这样第二遍运行 cmake -B build 时就可以直接用缓存的值,就不需要再检测一遍了。 如何清除缓存?删 build 大法了解一下 然而有时候外部的情况有所更新,这时候 CMake 里缓存的却是旧的值,会导致一系列问题。 这时我们需要清除缓存,最简单的办法就是删除 build 文件夹,然后重新运行 cmake -B build 。缓存是很多 set 的 PARENT_SCOPE 选项,把一个变量传递到上一层作用域(也就是父模 块)。 如果子模块需要向父模块里传变量怎么办? • 如果父模块里没有定义 MYVAR 的话,也可以用缓存变量向外部传变量(不推荐)。但是 这样就不光父模块可见了,父模块的父模块,到处都可见。 除了父子模块之外还有哪些是带独立作用域的 • include 的 XXX.cmake 没有独立作用域 • add_subdirectory0 码力 | 166 页 | 6.54 MB | 1 年前3C++高性能并行编程与优化 - 课件 - 09 CUDA C++ 流体仿真实战
进一步改进 VDB 导出:支持导出多个网格,并指定名称 进一步改进 VDB 导出: P-IMPL 模式 进一步改进 VDB 导出: F-IMPL 模式 Blender 渲染结果 改进 改进边界条件:外部边界流出而不是反弹,内部边界可以流出速度 Blender 中调整一下材质 Blender 中调整一下材质 改进对流:让烟雾随时间逐渐褪色 改进对流:让烟雾随时间逐渐褪色 改进褪色:不是褪色0 码力 | 58 页 | 14.90 MB | 1 年前3C++高性能并行编程与优化 - 课件 - 06 TBB 开启的并行编程之旅
用了工作窃取法来分配任务: 当一个线程 t1 做完自己队列里全部的工 作时,会从另一个工作中线程 t2 的队列 里取出任务,以免 t1 闲置浪费时间。 • 因此内部 for 循环有可能“窃取”到另一个 外部 for 循环的任务,从而导致 mutex 被重复上锁。 解决 1 :用标准库的递归锁 std::recursive_mutex 解决 2 :创建另一个任务域,这样不同域之间就不会窃取工作0 码力 | 116 页 | 15.85 MB | 1 年前3
共 19 条
- 1
- 2