C++高性能并行编程与优化 - 课件 - 15 C++ 系列课:字符与字符串(printable character) 外, ASCII 还规定了一 类特殊的控制字符 (control character) : • 0 表示空字符(‘ \0’ ) • 9 表示 Tab 制表符(‘ \t’ ) • 10 表示换行(‘ \n’ ) • 13 表示回车(‘ \r’ ) • 27 表示 ESC 键(‘ \x1b’ ) • 127 表示 DEL 键(‘ \x7f’ )等 • 0~31 )强制终止程序,这时常常会看到 一个 ^C 的字样,就是这样出现的。这里我 们的 cat 程序收到 ^C 以后,就直接终止退 出了。 关于控制字符的一个冷知识 • 除此之外,因为 ^D 是“传输终止符”,还可以在控制 台输入 Ctrl+D 来关闭标准输入流,终止正在读取他 的程序。 • 小彭老师常用 Ctrl+D 来快速关闭一个 shell (和输入 exit 命令的效果一样)。 • 以及按 n 个字符作为一个子字 符串,删除后半部分。 “0 结尾字符串”知识点应用举例 • C 语言所谓的字符串类型 char * 实际上就是个首地址指 针,如果让首地址指针向前移动 n 位,那就实现删除前 n 个字符的效果,而不用实际修改数组本身(更高效)。 C 语言转义符 • 常见的转义符: • ‘\n’ 换行符:另起一行(光标移到下一行行首) • ‘\r’ 回车符:光标移到行首(覆盖原来的字符)0 码力 | 162 页 | 40.20 MB | 1 年前3
C++高性能并行编程与优化 - 课件 - 13 C++ STL 容器全解之 vector比如右边这段代码会得到 4 。 • size_t size() const noexcept; vector 容器: operator[] • 要访问 vector 里的元素,只需用 [] 运算符 : • 例如 a[0] 访问第 0 个元素(人类的第一 个) • 例如 a[1] 访问第 1 个元素(人类的第二 个) • int &operator[](size_t i) noexcept; noexcept; • int const &operator[](size_t i) const noexcept; vector 容器: operator[] • 值得注意的是, [] 运算符在索引超出数组大 小时并不会直接报错,这是为了性能的考虑。 • 如果你不小心用 [] 访问了越界的索引,可能 会覆盖掉别的变量导致程序行为异常,或是访 问到操作系统未映射的区域导致奔溃。 • int 元素全为 0 的数组。 • vector(initializer_listlist); • explicit vector(size_t n); vector 容器 • 添加一个运算符重载用于打印 vector 类 型。 vector 容器:构造函数 • vector 的这个显式构造函数,默认会把所有元 素都初始化为 0 (不必手动去 memset )。 • 如果是其他自定义类,则会调用元素的默认构造 0 码力 | 90 页 | 4.93 MB | 1 年前3
C++高性能并行编程与优化 - 课件 - 12 从计算机组成原理看 C 语言指针long unsigned long signed long long unsigned long long 其实 C 语言也有 signed 修饰符,但是因为不加默认就是 signed 的,所以其实没有使用 signed 的必要。 字面常量:通过修饰符来确定 • 在数字后面追加 U 和 L 可以表示不同类型的字面常量,例如: • 32 是 int 类型 • 32L 是 long 类型 • 指针,顾名思义,就是“指向”一个内存中的变量。 • 语法规定:任何类型 T 所对应的指针类型是 T* 。 • 可以通过 & 运算符获取一个变量的指针(地址) 。 • 可以通过 * 运算符访问指针指向的变量(左值) 。 • 因此指针指向了变量,通过指针的 * 运算符写入 的值,会造成原变量也改变,这正是指针的用法。 float 类型对应的指针类型: float* • 任何类型都有相应的指针类型。 地址 字节 指针 p 的内容实际上就是一个整数 4 ,也就是变量 x 中第一个字节的门牌号。 因为 int 类型的四个字节都是紧挨着,所以只需要知道第一个字节的地址就行了。 这样等会通过 * 运算符访问的时候,就可以访问从门牌号 4 开始的一连串四个字节组成的 int 。 注意这里的指针 p 只有四字节,这是 32 位系统上的情况。 如果是 64 位系统,指针 p 将会是八字节的。 指针的本质是内存地址0 码力 | 128 页 | 2.95 MB | 1 年前3
C++高性能并行编程与优化 - 课件 - 14 C++ 标准库系列课 - 你所不知道的 set 容器的排序: string 会按“字典序”来排 • set 会从小到大排序,对 int 来 说就是数值的大小比较。那么对 字符串类型 string 要怎么排序 呢? • 其实 string 类定义了运算符重 载 < ,他会按字典序比较两个 字符串。所谓字典序就是优先比 较两者第一个字符(按 ASCII 码比较),如果相等则继续比较 下一个,不相等则直接以这个比 较的结果返回。如果比到末尾都 相等且字符串长度一样,则视为 型,例如 int 或 string 等。 • 第二个 CompT 定义了你想 要的比较函子, set 内部会 调用这个函数来决定怎么排 序。 • 如果 CompT 不指定,默认 会直接用运算符 < 来比较。 • 这里我们定义个 MyComp 作为比较函子,和默认的一 样用 < 来比较,所以没有变 化。 set 的排序:自定义排序函数 • 恶搞一下,这里我们把比较 函子 MyComp 的排序:自定义排序函数 • 首先搞懂 set 内部是怎么确定 两个元素 a 和 b 相等的: • !(a < b) && !(b < a) • 也就是说他 set 内部没有用到 == 运算符,而是调用了两次 比较函子来判断的。逻辑是: • 若 a 不小于 b 且 b 不小于 a ,则视为 a 等于 b ,所以 这就是为什么 set 只需要一个 比较函子,不需要相等函子的 原因。 set0 码力 | 83 页 | 10.23 MB | 1 年前3
C++高性能并行编程与优化 - 课件 - 08 CUDA 开启的 GPU 编程host 代码和 device 代码写在同一个文件内,这 是 OpenCL 做不到的。 编写一段在 GPU 上运行的代码 • 定义函数 kernel ,前面加上 __global__ 修 饰符,即可让他在 GPU 上执行。 • 不过调用 kernel 时,不能直接 kernel() ,而 是要用 kernel<<<1, 1>>>() 这样的三重尖括 号语法。为什么?这里面的两个 1 有什么用 CPU 上。 定义在 CPU 上的主机函数 • CUDA 完全兼容 C++ ,因此任何函数如 果没有指明修饰符,则默认就是 __host__ ,即 CPU 上的函数。 同时定义在 CPU 和 GPU 上 • 通过 __host__ __device__ 这样的双重修 饰符,可以把函数同时定义在 CPU 和 GPU 上,这样 CPU 和 GPU 都可以调 用。 让 constexpr 1 。 • 比如 n 为 65535 ,那么最后 127 个元 素是没有赋值的。 解决边角料难题 • 解决方法就是:采用向上取整的除法。 • 可是 C 语言好像没有向上整除的除法这 个运算符?没关系,用这个式子即可: • (n + nthreads - 1) / nthreads • 例如: (7 + 3) / 4 = 2 , (8 + 3 / 4) = 2 。 • 由于向上取整,这样会多出来一些线程,0 码力 | 142 页 | 13.52 MB | 1 年前3
C++高性能并行编程与优化 - 课件 - 07 深入浅出访存优化缓存会查找和该地址匹配的条目。如果找到,则修改缓存 中该地址的数据。如果找不到,则创建一个新条目来存储 CPU 写的数据,并标记为脏( dirty )。 • 当读和写创建的新条目过多,缓存快要塞不下时,他会把 最不常用的那个条目移除,这个现象称为失效( invalid )。 如果那个条目是被标记为脏的,则说明是当时打算写入的 数据,那就需要向主内存发送写入请求,等他写入成功, 才能安全移除这个条目。 malloc 时,操作系统并不会实际分配那一块内存,而是将这一段内存标记 为“不可用”。当用户试图访问(写入)这一片内存时,硬件就会触发所谓的缺页中断 ( page fault ),进入操作系统内核,内核会查找当前进程的 malloc 历史记录。如果发 现用户写入的地址是他曾经 malloc 过的地址区间,则执行实际的内存分配,并标记该段 内存为“可用”,下次访问就不会再产生缺页中断了;而如果用户写入的地址根本不是他 如有多个线程,每个线程保留一个 tmp 对象的副本, 防止多线程调用 func 出错。 • 返回时(或者进入时)调用 tmp.clear() 清除已有数据。 由于 vector 的特性,他只会把 size() 标记为 0 并调 用其成员的解构函数,而不会实际释放内存( free )。 • 因此第二次进入的时候,如果 n 不超过上一次的大小 ,就还是用的第一次分配的内存,避免了重新分配的开 销。对 func0 码力 | 147 页 | 18.88 MB | 1 年前3
C++高性能并行编程与优化 - 课件 - 性能优化之无分支编程 Branchless Programming性能优化 之 无分支编程 Branchless Programming by 彭于斌( @archibate ) 两种代码写法:分支 vs 三目运算符 两种使用方式:排序 vs 不排序 测试结果(均为 gcc -O3 ) 测试结果可视化 图表比较:分支 vs 无分支 分支 无分支 0 0.01 0.02 0.03 耗时(越低越好) 乱序 有序 • 传统的分支方法实现的 或者更满足“对称强迫症”的: • (cond) * a + !(cond) * b // 方法 2 • 还有一种“摆烂”的做法: • (cond ? a : b) // 方法 3 • 三目运算符通常会变成和 if-else 一样的分 支,同样会生成条件跳转指令,理应一样 低效。但是有时候编译器会检测到,可以 帮你自动优化成无分支版本的。 “ 妙用加减乘”进行无分支优化的通用公式 • __declspec(noinline) 才能编译。 不同写法的性能测试 可以看到不论是哪个优化级别,“妙用加减乘”的效果都是碾压 ifelse 的。 “ 摆大烂”的效果和 ifelse 几乎一样,也就是说根本没用,三目运算符还是生成了 低效的跳转指令,自己不上进,还指望编译器来救你?你还不如坐等天上掉馅饼。 从汇编角度分析( -O0 ) 从汇编角度分析( -O3 ) 因为 clamp 用了两次分支, if-else-if-else0 码力 | 47 页 | 8.45 MB | 1 年前3
C++高性能并行编程与优化 - 课件 - 10 从稀疏数据结构到量化数据类型) = 1 字节…… 小彭老师解决:访问者模式 把写入过的块地址缓存起来,可以避免多次访问全局表的开销。缓存在访问 者 (accessor) 的成员 map 里。访问者对象被我用 OpenMP 标记为 firstprivate ,意味着这个 map 是线程局部的,因此对他的访问不需要加锁, 更快。 应用在刚刚的 SNode 系统中 std::unordered_map 不支持 omp parallel malloc 时,操作系统并不会实际分配那一块内存,而是将这一段内存标记为“不可 用”。当用户试图访问(写入)这一片内存时,硬件就会触发所谓的缺页中断( page fault ),进入操作系统内核,内核会查找当前进程的 malloc 历史记录。如果发现用户写 入的地址是他曾经 malloc 过的地址区间,则执行实际的内存分配,并标记该段内存为“可 用”,下次访问就不会再产生缺页中断了;而如果用户写入的地址根本不是他0 码力 | 102 页 | 9.50 MB | 1 年前3
C++高性能并行编程与优化 - 课件 - 03 现代 C++ 进阶:模板元编程作为虚函数。然后定义: Numeric *twice(Numeric *t) { return t->multiply(2); } 且不说这样的性能问题,你忍得住寂寞去重复定义好 几个,然后每个运算符都要声明一个纯虚函数吗? 而且, Float 的乘法应该是 multiply(float) ,你也去 定义好几个重载吗?定义为 multiply(Numeric *) 的话 依然会违背你们的开 - 闭原则:比如 调用了这个函数,才会被编译,才会报错! • 用一个假模板实现延迟编译的技术,可以加快编译的速度,用于代理模式等。 模板函数:一个例子 • 比如,要打印任意一个 vector : 模板函数:配合运算符重载 • 实现用 std::cout << a 打印任意 vector : 模板函数:大家学废了吗! 1. 类型作为参数: template2. 整数值作为参数: template 而已。 C++ 特性:常引用( int const & ) • 如果说 int & 相当于 int * ,那么 int const & 就相当于 int const * 。 • const 修饰符的存在,使得 ref 不能被写入(赋值)。 • 这样的好处是更加安全(编译器也能够放心大胆地做自动优化): 自动类型推导:定义引用( auto & ) • 当然, auto 也可以用来定义引用,只需要改成 0 码力 | 82 页 | 12.15 MB | 1 年前3
C++高性能并行编程与优化 - 课件 - 17 由浅入深学习 map 容器。 • 也就是说 Python 的 [] 其实是调用了两个不同的运算符重载: • m[key] = val 实际上是 m.__setitem__(key, val) 。 • val = m[key] 实际上是 val = m.__getitem__(key) 。 • C++ 的 [] 就不论读取还是写入都是同一个运算符重载,他只是返回引用,无法区分你是读是写: • value_type 值坑了他。所以他们又另起炉灶,发明了越界时不会自动创建零值,而是能抛出异常的 at 函数。 C++ 和 Python 用法对比 C++ 和 Python 用法对比(运算符重载展开成普通函数后) 简单粗暴的 Java 用法 • 与 Python 和 C++ 不同, Java 放弃了花里胡哨的运算符重载,索性都采用成员函数 get put 来表示,非常明确。主要是为了把 get 和 put 作为接口函数,可以对应多个具体 实现。0 码力 | 90 页 | 8.76 MB | 1 年前3
共 18 条
- 1
- 2













