C++高性能并行编程与优化 - 课件 - 13 C++ STL 容器全解之 vectorvectora{4}; • 会得到长度为 1 只有一个元素 4 的数组。 • 如果需要长度为 4 ,元素全部为 0 的数组,必 须用圆括号 () 而不是花括号 {} ,这样才能保证 调用他的显式( explicit )构造函数: • vector a(4); • 会得到长度为 4 元素全为 0 的数组。 • vector(initializer_list list); 容器:构造函数 • 这在对于只能用花括号初始化的类成员来说,就 有很大问题: • vector a{4}; • 会得到长度为 1 只有一个元素 4 的数组。 • 但还是可以用这种写法强制调用显式构造函数: • vector a = vector (4); • 会得到长度为 4 元素全为 0 的数组。 • vector(initializer_list list); 容器:构造函数 • 这在对于只能用花括号初始化的类成员来说,就 有很大问题: • vector a{4}; • 会得到长度为 1 只有一个元素 4 的数组。 • 但还是可以用这种写法强制调用显式构造函数: • vector a = vector (4); • 会得到长度为 4 元素全为 0 的数组。 • vector(initializer_list list); 0 码力 | 90 页 | 4.93 MB | 1 年前3
C++高性能并行编程与优化 - 课件 - 01 学 C++ 从 CMake 学起g++ main.cpp -o a.out • 该命令会调用编译器程序 g++ ,让他读取 main.cpp 中的字符串(称为源码),并根据 C+ + 标准生成相应的机器指令码,输出到 a.out 这个文件中,(称为可执行文件)。 • > ./a.out • 之后执行该命令,操作系统会读取刚刚生成的可执行文件,从而执行其中编译成机器码, 调用系统提供的 printf 函数,并在终端显示出 Hello ,之后再根据一系列对象文件进行链接 ,得到最终的 a.out : • > g++ hello.o main.o -o a.out 为什么需要构建系统( Makefile ) • 文件越来越多时,一个个调用 g++ 编译链接会变得很麻烦。 • 于是,发明了 make 这个程序,你只需写出不同文件之间的依赖关系,和生成各文件的规则。 • > make a.out • 敲下这个命令,就可以构建出 a shell 或 python 可以做很多判断等。 4. 不同的编译器有不同的 flag 规则,为 g++ 准备的参数可能对 MSVC 不适用。 构建系统的构建系统( CMake ) • 为了解决 make 的以上问题,跨平台的 CMake 应运而生! • make 在 Unix 类系统上是通用的,但在 Windows 则不然。 • 只需要写一份 CMakeLists.txt ,他就能够在调用时生成当前系统所支持的构建系统。0 码力 | 32 页 | 11.40 MB | 1 年前3
C++高性能并行编程与优化 - 课件 - 02 现代 C++ 入门:RAII 内存管理RAII :避免犯错误 与 Java , Python 等垃圾回收语言不同, C++ 的 解构函数是显式的,离开作用域自动销毁,毫不含 糊(有好处也有坏处,对高性能计算而言利大于 弊) 如果没有解构函数,则每个带有返回的分 支都要手动释放所有之前的资源 : RAII :异常安全( exception-safe ) C++ 标准保证当异常发生时,会调用已创建对象的解构函数 。 因此 C++ 中没有(也不需要) • return {“ 佩奇” , 80}; • 的话,就不要加 explicit 。 • 顺便一提,上一个例子中 show(80) 和 show({80}) 等价。 使用 {} 和 () 调用构造函数,有什么区别? 1. int(3.14f) 不会出错,但是 int{3.14f} 会出错,因为 {} 是非强制转换。 2. Pig(“ 佩奇” , 3.14f) 不会出错,但是 Pig{“ 会出错,原因同上,更安全。 3. 可读性: Pig(1, 2) 则 Pig 有可能是个函数, Pig{1, 2} 看起来更明确。 • 其实谷歌在其 Code Style 中也明确提出别再通过 () 调用构造函数,需要类型转换时应该 用: 1. static_cast(3.14f) 而不是 int(3.14f) 2. reinterpret_cast (0xb8000) 0 码力 | 96 页 | 16.28 MB | 1 年前3
C++高性能并行编程与优化 - 课件 - 14 C++ 标准库系列课 - 你所不知道的 set 容器个模板参数: set• 第一个 T 是容器内元素的类 型,例如 int 或 string 等。 • 第二个 CompT 定义了你想 要的比较函子, set 内部会 调用这个函数来决定怎么排 序。 • 如果 CompT 不指定,默认 会直接用运算符 < 来比较。 • 这里我们定义个 MyComp 作为比较函子,和默认的一 样用 < 来比较,所以没有变 化。 的排序:自定义排序函数 • 首先搞懂 set 内部是怎么确定 两个元素 a 和 b 相等的: • !(a < b) && !(b < a) • 也就是说他 set 内部没有用到 == 运算符,而是调用了两次 比较函子来判断的。逻辑是: • 若 a 不小于 b 且 b 不小于 a ,则视为 a 等于 b ,所以 这就是为什么 set 只需要一个 比较函子,不需要相等函子的 原因。 set 的排序:自定义排序函数 这意味着如果一个 STL 模板函数(比如 std::find )要求迭代器是前向迭代器即可,那么也可 以给他随机访问迭代器,因为前向迭代器是随机访问迭代器的子集。 例如, vector 和 list 都可以调用 std::find ( set 则直接提供了 find 作为成员函数,稍后 讨论) set 和 vector 迭代器的不同点 • set 的迭代器对象也重载了 + + 为红黑树的遍历。 • 0 码力 | 83 页 | 10.23 MB | 1 年前3
C++高性能并行编程与优化 - 课件 - 11 现代 CMake 进阶指南--build build 统一了不同平台( Linux 上会调用 make , Windows 上调用 devenv.exe ) • 结论:从现在开始,如果在命令行操作 cmake ,请使用更方便的 -B 和 --build 命令。 // 在源码目录用 -B 直接创建 build 目录并生成 build/Makefile // 自动调用本地的构建系统在 build 里构建,即: make -C -C build -j4 // 调用本地的构建系统执行 install 这个目标,即安 装 -D 选项:指定配置变量(又称缓存变量) • 可见 CMake 项目的构建分为两步: • 第一步是 cmake -B build ,称为配置阶段( configure ),这时只检测环境并生成构建规则 • 会在 build 目录下生成本地构建系统能识别的项目文件( 目录下生成本地构建系统能识别的项目文件( Makefile 或是 .sln ) • 第二步是 cmake --build build ,称为构建阶段( build ),这时才实际调用编译器来编译代码 • 在配置阶段可以通过 -D 设置缓存变量。第二次配置时,之前的 -D 添加仍然会被保留。 • cmake -B build -DCMAKE_INSTALL_PREFIX=/opt/openvdb-8.0 • ↑ 设置安装路径为0 码力 | 166 页 | 6.54 MB | 1 年前3
C++高性能并行编程与优化 - 课件 - 17 由浅入深学习 map 容器end()) { • throw std::out_of_range(“ 找不到键值” ); • } • val = it->second; 从 map 中读取元素: C++ 和 Python 对比 • Python 中 val = m[“key”] 读取元素,找不到键值会出错,调试时更早发现错误。 • C++ 中 val = m[“key”] 读取元素,找不到键值不会出错而是默默创建,还初始化为 end()) { • throw std::out_of_range(“ 找不到键值” ); • } • it->second = val; 往 map 中写入元素: C++ 和 Python 对比 • Python 中 m[“key”] = val 写入元素,找不到键值会自动创建,并写入元素。 • C++ 中 m[“key”] = val 写入元素,找不到键值会自动创建,并写入元素。 • C++ 自动默默创建的特性反而是个优点了,如果用了 at() 反而会在插入新键值时莫名 其妙报错。此外 [] 默默创建以后把值初始化为 0 的特性,由于调用者是 = val 赋值,所 以初始化也没用了,反正马上会写入 val 。 浅谈这种精分设计的原因 • 总结,要符合你熟悉的 Python 的 [] 行为,在 C++ 中要根据不同情况选择不同的方法访 问: • 读取用 at() 写入用 [] •0 码力 | 90 页 | 8.76 MB | 1 年前3
C++高性能并行编程与优化 - 课件 - 05 C++11 开始的多线程编程值函数,但是提供了移动构造 / 赋值函数。 • 因此,当 t1 所在的函数退出时,就会调用 std::thread 的解构函数,这会销毁 t1 线程 。 • 所以, download 函数才会出师未捷身先死 ——还没开始执行他的线程就被销毁了。 解构函数不再销毁线程: t1.detach() • 解决方案:调用成员函数 detach() 分离该 线程——意味着线程的生命周期不再由当 前 程还是有点麻烦,我们可以自定义一个类 ThreadPool ,并用他创建一个全局变量, 其解构函数会在 main 退出后自动调用。 std::jthread :符合 RAII 思想,解构时自动 join() • C++20 引入了 std::jthread 类,和 std::thread 不同在于:他的解构函数里会 自动调用 join() 函数,从而保证 pool 解 构时会自动等待全部线程执行完毕。 小彭老师快乐吐槽时间 std::future 对象 。 • lambda 的函数体将在另一个线程里执行 。 • 接下来你可以在 main 里面做一些别的事 情, download 会持续在后台悄悄运行。 • 最后调用 future 的 get() 方法,如果此时 download 还没完成,会等待 download 完成,并获取 download 的返回值。 显示地等待: wait() • 除了 get()0 码力 | 79 页 | 14.11 MB | 1 年前3
C++高性能并行编程与优化 - 课件 - 10 从稀疏数据结构到量化数据类型语言 % 的特色:负数 • 7 % 4 = 3 • -7 % 4 = -3 • 也就是说 a % b 如果 a 是负数,则得到的模也是负数。 Python 的 % 就没问题 • 7 % 4 = 3 • -7 % 4 = 1 • Python 的模运算 a % b 的值始终是 [0, b) 区间内的正数,非常方便。 对稀疏数据结构造成的问题 • 如果这里的 x 是负数,则 x % B / 4 = 1 • -7 / 4 = -1 • 也就是说 a / b ,如果 a 是负数,则是向上取整,如果 a 是正数,则是向下取整。 Python 的 // 就没问题 • 7 // 4 = 1 • -7 // 4 = -2 • Python 的整除运算 a // b 的值始终是向下取整,非常方便。 对稀疏数据结构造成的问题 • 也就是说,如果 x 是 [-3,0] 则 x / B >> 3 。 >> 2 = 位运算 >> 对负数的处理 signed 类型的 >> n 会把最高位复制 n 次。 因为补码的特性,这导致负数 >> 的结果仍是负 数。 这样就实现了和 Python 一样的始终向下取整除 法。 >> 2 = unsigned 类型的位运算 >> 不一样 而 unsigned 类型的 >> n 会不会复制最高位, 只是单纯的位移,这会导致负数的符号位单独被位0 码力 | 102 页 | 9.50 MB | 1 年前3
C++高性能并行编程与优化 - 课件 - 08 CUDA 开启的 GPU 编程代码写在同一个文件内,这 是 OpenCL 做不到的。 编写一段在 GPU 上运行的代码 • 定义函数 kernel ,前面加上 __global__ 修 饰符,即可让他在 GPU 上执行。 • 不过调用 kernel 时,不能直接 kernel() ,而 是要用 kernel<<<1, 1>>>() 这样的三重尖括 号语法。为什么?这里面的两个 1 有什么用 ?稍后会说明。 • 运行以后,就会在 • 这是因为 GPU 和 CPU 之间的通信,为了高效,是异 步的。也就是 CPU 调用 kernel<<<1, 1>>>() 后,并不 会立即在 GPU 上执行完毕,再返回。实际上只是把 kernel 这个任务推送到 GPU 的执行队列上,然后立即 返回,并不会等待执行完毕。 • 因此可以调用 cudaDeviceSynchronize() ,让 CPU 陷 入等待,等 GPU 完成队列的所有任务后再返回。从而 用,可以有参数,不可以有返回值。 • 而 __device__ 则用于定义设备函数,他在 GPU 上执行,但是从 GPU 上调用的,而 且不需要三重尖括号,和普通函数用起来一 样,可以有参数,有返回值。 • 即: host 可以调用 global ; global 可以调 用 device ; device 可以调用 device 。 声明为内联函数 • 注意, inline 在现代 C++ 中的效果是声明一个函数为0 码力 | 142 页 | 13.52 MB | 1 年前3
C++高性能并行编程与优化 - 课件 - 15 C++ 系列课:字符与字符串编译器会 给出警告),但是运行结果不对,或者还有可能崩溃。 泛型的 iostream 应运而生 • 得益于 C++ 的重载技术, cout 不用你手动指定类型,他 会自动识别参数的类型,帮你调用相应的格式化函数。 c_str 和 data 的区别 • s.c_str() 保证返回的是以 0 结尾的字符串首地址指针,总长度为 s.size() + 1 。 • s.data() 只保证返回长度为 char * 可以隐式转换为 string (为了方便) • string 不可以隐式转换为 const char * (安全起见) • 如果确实需要从 string 转换为 const char * ,请调用 .c_str() 这个成员函数 。 字符串的连接( + 运算符) • C 语言规定,双引号包裹的字符串是 const char * 类型的,他们没有 + 运算 符。 • C++ 为了向前兼容,没办法改变 这么方便,能不能让他不要直接输出到控制台,而是把结果存到一个字 符串呢?这正是字符串流 stringstream 的作用。 • 和 cout 完全一样,同样的 << 和 hex 选项。 • 但是他的输出会保存到一个字符串里。 • 调用成员函数 .str() 就能取出这个字符串了。 • 之后这个字符串就可以用作其他用途,比如 printf 打印,或者用于查询数据库,都没问题。 • 这里比较无聊,最后还是直接输出到了 cout0 码力 | 162 页 | 40.20 MB | 1 年前3
共 31 条
- 1
- 2
- 3
- 4













