C++高性能并行编程与优化 - 课件 - 07 深入浅出访存优化)。 • 并行能减轻计算瓶颈,但不减轻内存瓶颈,故后者是优化的重点 。 浮点加法的计算量 • 冷知识:并行地给浮点数组每个元素做一次加法反而更慢。 • 因为一次浮点加法的计算量和访存的超高延迟相比实在太少了。 • 计算太简单,数据量又大,并行只带来了多线程调度的额外开销 。 • 小彭老师经验公式: 1 次浮点读写 ≈ 8 次浮点加法 • 如果矢量化成功( SSE ): 1 次浮点读写 cycle , 符合小彭老师的经验公式。 • “right” 和“ wrong” 指的是分支预测是否成功。 多少计算量才算多? • 看右边的 func ,够复杂了吧?也只是勉勉强强超过一 点内存的延迟了,但在 6 个物理核心上并行加速后, 还是变成 mem-bound 了。 • 加速比: 1.36 倍 • 应该达到 6 倍(物理核心数量)才算理想加速比。 加速曲线 • funcA 用了 可见数据量较小时,实际带宽甚至超过了 理论带宽极限 42672 MB/s ! • 而数据量足够大时, 才回落到正常的带宽 。 • 这是为什么? CPU 内部的高速缓存 • 原来 CPU 的厂商早就意识到了内存延迟高,读写效率低 下的问题。因此他们在 CPU 内部引入了一片极小的存储 器——虽然小,但是读写速度却特别快。这片小而快的 存储器称为缓存( cache )。 • 当 CPU 访问某个地址时,会先查找缓存中是否有对应的0 码力 | 147 页 | 18.88 MB | 1 年前3
C++高性能并行编程与优化 - 课件 - 08 CUDA 开启的 GPU 编程稍微快一些,但不完全精确的 __sinf • 两个下划线的 __sinf 是 GPU intrinstics ,精度相当于 GLSL 里的那种。 适合对精度要求不高,但有性能要求的图 形学任务。 • 类似的这样的低精度內建函数还有 __expf 、 __logf 、 __cosf 、 __powf 等。 • 还有 __fdividef(x, y) 提供更快的浮点除法 ,和一般的除法有相同的精确度,但是在 避免修改寄存器和访存相当于 CPU 的 SIMD 指令 _mm_blendv_ps 和 _mm_store_mask_ps ,不过 GPU 这种 SIMT 的设计能够自动处理分支和循环的分歧,这是 他门槛比 CPU 低的一点。 避免修改寄存器和访存相当于 CPU 的 SIMD 指令 _mm_blendv_ps 和 _mm_store_mask_ps ,不过 GPU 这种 SIMT 的设计能够自动处理分支和循环的分歧,这是 他门槛比 CPU 低的一点。0 码力 | 142 页 | 13.52 MB | 1 年前3
C++高性能并行编程与优化 - 课件 - 10 从稀疏数据结构到量化数据类型2 = 位运算总结 • a >> n 可以取出 a 的高 32-n 位 • a & ((1 << n) - 1) 可以取出 a 的低 n 位 • (a >> n) | (b & ((1 << n) - 1)) 可以取出 a 的高 32-n 位, b 的低 n 位,组合成一个 32 位的整数。 解决: & 替代 % , >> 替代 / , | 替代 + 块编号直接为对齐的坐标, << )。 • 右边就是一个很好的例子。 使用 int64_t :每个占据 8 字节 • 如果用更大的数据类型,用时会直接提升两倍! • 这是因为 i % 2 的计算时间,完全隐藏在内存 的超高延迟里了。 • 可见,当数据量足够大,计算量却不多时,读写 数据量的大小唯一决定着你的性能。 • 特别是并行以后,计算量可以被并行加速,而访 存却不行。 使用 int8_t :每个占据 1 字节 :标准库帮你实现好了 • 其实标准库的 vector是一个特化的版本 ,他会自动像刚刚说的把值看做 1bit ,然后八个 合并成一个 int8_t 。 • 不过效率比我们手写的低很多…… 不推荐使用 std::vector • 不建议使用 vector 的原因:他返回的不是真正的引用,而是一个重载了 operator= 和 operator bool 0 码力 | 102 页 | 9.50 MB | 1 年前3
C++高性能并行编程与优化 - 课件 - 06 TBB 开启的并行编程之旅O(n²) 表示花费时间和数据量的平方成正比。 • 对于并行算法,复杂度的评估则要分为两种: • 时间复杂度:程序所用的总时间(重点) • 工作复杂度:程序所用的计算量(次要) • 这两个指标都是越低越好。时间复杂度决定了快慢,工作复杂度决定了耗电量。 • 通常来说,工作复杂度 = 时间复杂度 * 核心数量 • 1 个核心工作一小时, 4 个核心工作一小时。时间复杂度一样,而后者工作复杂度更高。 域按照常规的两层循环访问以便矢量化,块外 部大区域则以类似 Z 字型的曲线遍历,这样 能保证每次访问的数据在地址上比较靠近,并 且都是最近访问过的,从而已经在缓存里可以 直接读写,避免了从主内存读写的超高延迟。 • 下次课会进一步深入探讨访存优化,详细剖析 这个案例,那么下周六 14 点敬请期待。 第 6 章:并发容器 std::vector 扩容时会移动元素 • std::vector 内部存储了一个指针,指向一段容量 push_back 等一起用,否则需要用读写锁 保护。 不建议通过索引随机访问 • 因为 tbb::concurrent_vector 内存不连续 的特点,通过索引访问,比通过迭代器访 问的效率低一些。 • 因此不推荐像 a[i] 这样通过索引随机访问 其中的元素, *(it + i) 这样需要迭代器跨步 访问的也不推荐。 推荐通过迭代器顺序访问 • 最好的方式是用 begin() 和0 码力 | 116 页 | 15.85 MB | 1 年前3
Hello 算法 1.1.0 C++ 版ASCII 码是最早出现的字符集,其全称为 American Standard Code for Information Interchange(美国 标准信息交换代码)。它使用 7 位二进制数(一个字节的低 7 位)表示一个字符,最多能够表示 128 个不同的 字符。如图 3‑6 所示,ASCII 码包括英文字母的大小写、数字 0 ~ 9、一些标点符号,以及一些控制字符(如 换行符和制表符)。 图 ‧ 缓存局部性:当访问数组元素时,计算机不仅会加载它,还会缓存其周围的其他数据,从而借助高速缓 存来提升后续操作的执行速度。 连续空间存储是一把双刃剑,其存在以下局限性。 ‧ 插入与删除效率低:当数组中元素较多时,插入与删除操作需要移动大量的元素。 ‧ 长度不可变:数组在初始化后长度就固定了,扩容数组需要将所有数据复制到新数组,开销很大。 ‧ 空间浪费:如果数组分配的大小超过实际所需,那么多余的空间就被浪费了。 的成本是硬盘的几十倍,这使得它难以在消费者市场普及。 ‧ 缓存的大容量和高速度难以兼得。随着 L1、L2、L3 缓存的容量逐步增大,其物理尺寸会变大,与 CPU 核心之间的物理距离会变远,从而导致数据传输时间增加,元素访问延迟变高。在当前技术下,多层级 的缓存结构是容量、速度和成本之间的最佳平衡点。 图 4‑9 计算机存储系统 Note 计算机的存储层次结构体现了速度、容量和成本三者之间的精妙平衡。实际上,这种权衡普遍存在于0 码力 | 379 页 | 18.47 MB | 1 年前3
Hello 算法 1.0.0 C++版码」是最早出现的字符集,其全称为 American Standard Code for Information Interchange(美 国标准信息交换代码)。它使用 7 位二进制数(一个字节的低 7 位)表示一个字符,最多能够表示 128 个不 同的字符。如图 3‑6 所示,ASCII 码包括英文字母的大小写、数字 0 ~ 9、一些标点符号,以及一些控制字符 (如换行符和制表符)。 图 ‧ 缓存局部性:当访问数组元素时,计算机不仅会加载它,还会缓存其周围的其他数据,从而借助高速缓 存来提升后续操作的执行速度。 连续空间存储是一把双刃剑,其存在以下局限性。 ‧ 插入与删除效率低:当数组中元素较多时,插入与删除操作需要移动大量的元素。 ‧ 长度不可变:数组在初始化后长度就固定了,扩容数组需要将所有数据复制到新数组,开销很大。 ‧ 空间浪费:如果数组分配的大小超过实际所需,那么多余的空间就被浪费了。 的成本是硬盘的几十倍,这使得它难以在消费者市场普及。 ‧ 缓存的大容量和高速度难以兼得。随着 L1、L2、L3 缓存的容量逐步增大,其物理尺寸会变大,与 CPU 核心之间的物理距离会变远,从而导致数据传输时间增加,元素访问延迟变高。在当前技术下,多层级 的缓存结构是容量、速度和成本之间的最佳平衡点。 图 4‑9 计算机存储系统 � 计算机的存储层次结构体现了速度、容量和成本三者之间的精妙平衡。实际上,这种权衡普遍0 码力 | 378 页 | 17.59 MB | 1 年前3
Hello 算法 1.2.0 简体中文 C++ 版ASCII 码是最早出现的字符集,其全称为 American Standard Code for Information Interchange(美国 标准信息交换代码)。它使用 7 位二进制数(一个字节的低 7 位)表示一个字符,最多能够表示 128 个不同的 字符。如图 3‑6 所示,ASCII 码包括英文字母的大小写、数字 0 ~ 9、一些标点符号,以及一些控制字符(如 换行符和制表符)。 图 ‧ 缓存局部性:当访问数组元素时,计算机不仅会加载它,还会缓存其周围的其他数据,从而借助高速缓 存来提升后续操作的执行速度。 连续空间存储是一把双刃剑,其存在以下局限性。 ‧ 插入与删除效率低:当数组中元素较多时,插入与删除操作需要移动大量的元素。 ‧ 长度不可变:数组在初始化后长度就固定了,扩容数组需要将所有数据复制到新数组,开销很大。 ‧ 空间浪费:如果数组分配的大小超过实际所需,那么多余的空间就被浪费了。 的成本是硬盘的几十倍,这使得它难以在消费者市场普及。 ‧ 缓存的大容量和高速度难以兼得。随着 L1、L2、L3 缓存的容量逐步增大,其物理尺寸会变大,与 CPU 核心之间的物理距离会变远,从而导致数据传输时间增加,元素访问延迟变高。在当前技术下,多层级 的缓存结构是容量、速度和成本之间的最佳平衡点。 图 4‑9 计算机存储系统 Tip 计算机的存储层次结构体现了速度、容量和成本三者之间的精妙平衡。实际上,这种权衡普遍存在于0 码力 | 379 页 | 18.48 MB | 10 月前3
《深入浅出MFC》2/ewMinute, st.wSecond); SetDlgItemText (_hWndDlg, IDE_TIMER, str); Sleep (1000); // 延迟一秒。 } } 46 以_beginthreadex 取代CreateThread 别忘了Windows 程序除了调用Win32 API,通常也很难避免调用任何一个C runtime 函 *(ThreadArg), rect.bottom-(dwThreadHits/10), *(ThreadArg)+0x40, rect.bottom); // 延迟. . . if (_uDelayType == SLEEPDELAY) Sleep(10); else if (_uDelayType == FORLOOPDELAY) leep(10),意思是先睡10 个 毫秒,之后再醒来;这段期间,CPU 可以给别人使用。第二种方式是以空循环30000 次 做延迟;空循环期间CPU 不能给别人使用(事实上CPU 正忙碌于那30000 次空转)。 52 图1-9 是执行画面。注意,先选择延迟方式("for loop delay" 或"sleep delay"),再按下 【Resume Thread】。如果你选择¡ §for0 码力 | 1009 页 | 11.08 MB | 1 年前3
Hello 算法 1.2.0 繁体中文 C++ 版碼是最早出現的字符集,其全稱為 American Standard Code for Information Interchange(美國 標準資訊交換程式碼)。它使用 7 位二進位制數(一個位元組的低 7 位)表示一個字元,最多能夠表示 128 個 不同的字元。如圖 3‑6 所示,ASCII 碼包括英文字母的大小寫、數字 0 ~ 9、一些標點符號,以及一些控制字 元(如換行符和製表符)。 圖 ‧ 快取區域性:當訪問陣列元素時,計算機不僅會載入它,還會快取其周圍的其他資料,從而藉助高速快 取來提升後續操作的執行速度。 連續空間儲存是一把雙刃劍,其存在以下侷限性。 ‧ 插入與刪除效率低:當陣列中元素較多時,插入與刪除操作需要移動大量的元素。 ‧ 長度不可變:陣列在初始化後長度就固定了,擴容陣列需要將所有資料複製到新陣列,開銷很大。 ‧ 空間浪費:如果陣列分配的大小超過實際所需,那麼多餘的空間就被浪費了。 所示,假設我們想在相鄰的兩個節點 n0 和 n1 之間插入一個新節 點 P ,則只需改變兩個節點引用(指標)即可,時間複雜度為 ?(1) 。 相比之下,在陣列中插入元素的時間複雜度為 ?(?) ,在大資料量下的效率較低。 圖 4‑6 鏈結串列插入節點示例 // === File: linked_list.cpp === /* 在鏈結串列的節點 n0 之後插入節點 P */ void insert(ListNode0 码力 | 379 页 | 18.79 MB | 10 月前3
C++高性能并行编程与优化 - 课件 - 03 现代 C++ 进阶:模板元编程列出所有模板参数的排列组合 ,违背了开 - 闭原则。 模板的惰性:延迟编译 • 要证明模板的惰性,只需看这个例子: • 要是编译器哪怕细看了一眼:字符串怎么可能被写入呢?肯定是会出错的。 • 但是却没有出错,这是因为模板没有被调用,所以不会被实际编译! • 而只有当 main 调用了这个函数,才会被编译,才会报错! • 用一个假模板实现延迟编译的技术,可以加快编译的速度,用于代理模式等。 模板函数:一个例子0 码力 | 82 页 | 12.15 MB | 1 年前3
共 20 条
- 1
- 2













