 C++高性能并行编程与优化 -  课件 - 04 从汇编角度看编译器优化错了,有可能不仅不变快,反而还变慢! SIMD 和缓存行对齐只是性能优 化的一个点,又不是全部。还要考虑结构体变大会导致内存带宽的占用, 对缓存的占用等一系列连锁反应,总之,要根据实际情况选择优化方案。 结构体的内存布局: AOS 与 SOA • AOS ( Array of Struct )单个对象的属性紧挨着存 • xyzxyzxyzxyz • SOA ( Struct of Array )属性分离存储在多个数组 1 ,因为 abs 是个参数类型为 int 的函数。 为了避免这种麻烦,请用安全的 std::abs(1.4f) = 1.4f 开启前: sqrt 矢量化失败 开启后: sqrt 矢量化成功 嵌套循环:直接累加,有指针别名问题 编译器担心 c 和 a 可能会指向同一个 地址,而连续判断三个指针是否有重合 又过于复杂,无耻地放弃了矢量化! 解决方案 1 :先读到局部变量,累加完毕后,再写入0 码力 | 108 页 | 9.47 MB | 1 年前3 C++高性能并行编程与优化 -  课件 - 04 从汇编角度看编译器优化错了,有可能不仅不变快,反而还变慢! SIMD 和缓存行对齐只是性能优 化的一个点,又不是全部。还要考虑结构体变大会导致内存带宽的占用, 对缓存的占用等一系列连锁反应,总之,要根据实际情况选择优化方案。 结构体的内存布局: AOS 与 SOA • AOS ( Array of Struct )单个对象的属性紧挨着存 • xyzxyzxyzxyz • SOA ( Struct of Array )属性分离存储在多个数组 1 ,因为 abs 是个参数类型为 int 的函数。 为了避免这种麻烦,请用安全的 std::abs(1.4f) = 1.4f 开启前: sqrt 矢量化失败 开启后: sqrt 矢量化成功 嵌套循环:直接累加,有指针别名问题 编译器担心 c 和 a 可能会指向同一个 地址,而连续判断三个指针是否有重合 又过于复杂,无耻地放弃了矢量化! 解决方案 1 :先读到局部变量,累加完毕后,再写入0 码力 | 108 页 | 9.47 MB | 1 年前3
 C++高性能并行编程与优化 -  课件 - 07 深入浅出访存优化* * * * 重新认识 SOA * * * * 结构体的内存布局: AOS 与 SOA • AOS ( Array of Struct )单个对象的属性紧挨着存 • xyzxyzxyzxyz • SOA ( Struct of Array )属性分离存储在多个数组 ,因此比 AOS 快了 2 倍。 AOSOA :两者得兼 • 还有一种办法就是让 MyClass 内部是 SOA ,而外部仍是一个 vector C++高性能并行编程与优化 -  课件 - 07 深入浅出访存优化* * * * 重新认识 SOA * * * * 结构体的内存布局: AOS 与 SOA • AOS ( Array of Struct )单个对象的属性紧挨着存 • xyzxyzxyzxyz • SOA ( Struct of Array )属性分离存储在多个数组 ,因此比 AOS 快了 2 倍。 AOSOA :两者得兼 • 还有一种办法就是让 MyClass 内部是 SOA ,而外部仍是一个 vector- 的 AOS—— 这种内存布局称为 AOSOA 。 • 缺点是必须保证数量是 1024 的整数倍, 而且因为要两次指标索引,随机访问比较 烦。 • 这里的 1024 并非随意选取,而是要让每 个属性 SOA 数组的大小为一个页 平化用的公式 (z * ny + y) * nx + x 通过文本信 息发给他也可,他看完就知道是 ZYX 序了。 主流程序都会用 YX 序, ZYX 序。 二维数组的遍历 • 二维数组在内存中的布局有 YX 序, XY 序。 • 二维数组的循环遍历也有 YX 序, XY 序之分。 • 循环遍历,比如 YX 序:表示外层循环是以 Y 轴索 引为变量,内层循环是以 X 轴索引为变量。 • 比如右边的例子,我们分配了一个 0 码力 | 147 页 | 18.88 MB | 1 年前3
 C++高性能并行编程与优化 -  课件 - 06  TBB 开启的并行编程之旅SimBody 的源码路径,这么 dedicated 让人咋 用? 第 4 章:任务域与嵌套 https://link.springer.com/chapter/10.1007%2F978-1-4842-4398-5_12 任务域: tbb::task_arena 任务域:指定使用 4 个线程 嵌套 for 循环 嵌套 for 循环:死锁问题 死锁问题的原因 • 因为 TBB 用了工作窃取法来分配任务: 每一个步骤( filter )的输入和返回类型都可以不一 样。要求:流水线上一步的返回类型,必须和下一 步的输入类型一致。且第一步的没有输入,最后一 步没有返回,所以都为 void 。 • TBB 支持嵌套的并行,因此流水线内部也可以调用 tbb::parallel_for 进一步并行。 流水线的利弊 • 流水线式的并行,因为每个线程执行的指令之间往往 没有关系,主要适用于各个核心可以独立工作的0 码力 | 116 页 | 15.85 MB | 1 年前3 C++高性能并行编程与优化 -  课件 - 06  TBB 开启的并行编程之旅SimBody 的源码路径,这么 dedicated 让人咋 用? 第 4 章:任务域与嵌套 https://link.springer.com/chapter/10.1007%2F978-1-4842-4398-5_12 任务域: tbb::task_arena 任务域:指定使用 4 个线程 嵌套 for 循环 嵌套 for 循环:死锁问题 死锁问题的原因 • 因为 TBB 用了工作窃取法来分配任务: 每一个步骤( filter )的输入和返回类型都可以不一 样。要求:流水线上一步的返回类型,必须和下一 步的输入类型一致。且第一步的没有输入,最后一 步没有返回,所以都为 void 。 • TBB 支持嵌套的并行,因此流水线内部也可以调用 tbb::parallel_for 进一步并行。 流水线的利弊 • 流水线式的并行,因为每个线程执行的指令之间往往 没有关系,主要适用于各个核心可以独立工作的0 码力 | 116 页 | 15.85 MB | 1 年前3
 Hello 算法 1.1.0 C++ 版return res; } 总的来说,for 循环的代码更加紧凑,while 循环更加灵活,两者都可以实现迭代结构。选择使用哪一个应该 根据特定问题的需求来决定。 3. 嵌套循环 我们可以在一个循环结构内嵌套另一个循环结构,下面以 for 循环为例: // === File: iteration.cpp === /* 双层 for 循环 */ string nestedForLoop(int } } return res.str(); } 图 2‑2 是该嵌套循环的流程框图。 第 2 章 复杂度分析 hello‑algo.com 22 图 2‑2 嵌套循环的流程框图 在这种情况下,函数的操作数量与 ?2 成正比,或者说算法运行时间和输入数据大小 ? 成“平方关系”。 我们可以继续添加嵌套循环,每一次嵌套都是一次“升维”,将会使时间复杂度提高至“立方关系”“四次方 关系”,以此类推。 中的常数项。因为它们都与 ? 无关,所以对时间复杂度不产生影响。 2. 省略所有系数。例如,循环 2? 次、5? + 1 次等,都可以简化记为 ? 次,因为 ? 前面的系数对时间复 杂度没有影响。 3. 循环嵌套时使用乘法。总操作数量等于外层循环和内层循环操作数量之积,每一层循环依然可以分别 套用第 1. 点和第 2. 点的技巧。 给定一个函数,我们可以用上述技巧来统计操作数量: void algorithm(int0 码力 | 379 页 | 18.47 MB | 1 年前3 Hello 算法 1.1.0 C++ 版return res; } 总的来说,for 循环的代码更加紧凑,while 循环更加灵活,两者都可以实现迭代结构。选择使用哪一个应该 根据特定问题的需求来决定。 3. 嵌套循环 我们可以在一个循环结构内嵌套另一个循环结构,下面以 for 循环为例: // === File: iteration.cpp === /* 双层 for 循环 */ string nestedForLoop(int } } return res.str(); } 图 2‑2 是该嵌套循环的流程框图。 第 2 章 复杂度分析 hello‑algo.com 22 图 2‑2 嵌套循环的流程框图 在这种情况下,函数的操作数量与 ?2 成正比,或者说算法运行时间和输入数据大小 ? 成“平方关系”。 我们可以继续添加嵌套循环,每一次嵌套都是一次“升维”,将会使时间复杂度提高至“立方关系”“四次方 关系”,以此类推。 中的常数项。因为它们都与 ? 无关,所以对时间复杂度不产生影响。 2. 省略所有系数。例如,循环 2? 次、5? + 1 次等,都可以简化记为 ? 次,因为 ? 前面的系数对时间复 杂度没有影响。 3. 循环嵌套时使用乘法。总操作数量等于外层循环和内层循环操作数量之积,每一层循环依然可以分别 套用第 1. 点和第 2. 点的技巧。 给定一个函数,我们可以用上述技巧来统计操作数量: void algorithm(int0 码力 | 379 页 | 18.47 MB | 1 年前3
 Hello 算法 1.0.0b5 C++版return res; } 总的来说,for 循环的代码更加紧凑,while 循环更加灵活,两者都可以实现迭代结构。选择使用哪一个应该 根据特定问题的需求来决定。 3. 嵌套循环 我们可以在一个循环结构内嵌套另一个循环结构,以 for 循环为例: // === File: iteration.cpp === /* 双层 for 循环 */ string nestedForLoop(int } return res.str(); } 图 2‑2 给出了该嵌套循环的流程框图。 第 2 章 复杂度分析 hello‑algo.com 21 图 2‑2 嵌套循环的流程框图 在这种情况下,函数的操作数量与 ?2 成正比,或者说算法运行时间和输入数据大小 ? 成“平方关系”。 我们可以继续添加嵌套循环,每一次嵌套都是一次“升维”,将会使时间复杂度提高至“立方关系”、“四次方 关系”、以此类推。 中的常数项。因为它们都与 ? 无关,所以对时间复杂度不产生影响。 2. 省略所有系数。例如,循环 2? 次、5? + 1 次等,都可以简化记为 ? 次,因为 ? 前面的系数对时间复 杂度没有影响。 3. 循环嵌套时使用乘法。总操作数量等于外层循环和内层循环操作数量之积,每一层循环依然可以分别 套用第 1. 点和第 2. 点的技巧。 给定一个函数,我们可以用上述技巧来统计操作数量。 void algorithm(int0 码力 | 377 页 | 30.69 MB | 1 年前3 Hello 算法 1.0.0b5 C++版return res; } 总的来说,for 循环的代码更加紧凑,while 循环更加灵活,两者都可以实现迭代结构。选择使用哪一个应该 根据特定问题的需求来决定。 3. 嵌套循环 我们可以在一个循环结构内嵌套另一个循环结构,以 for 循环为例: // === File: iteration.cpp === /* 双层 for 循环 */ string nestedForLoop(int } return res.str(); } 图 2‑2 给出了该嵌套循环的流程框图。 第 2 章 复杂度分析 hello‑algo.com 21 图 2‑2 嵌套循环的流程框图 在这种情况下,函数的操作数量与 ?2 成正比,或者说算法运行时间和输入数据大小 ? 成“平方关系”。 我们可以继续添加嵌套循环,每一次嵌套都是一次“升维”,将会使时间复杂度提高至“立方关系”、“四次方 关系”、以此类推。 中的常数项。因为它们都与 ? 无关,所以对时间复杂度不产生影响。 2. 省略所有系数。例如,循环 2? 次、5? + 1 次等,都可以简化记为 ? 次,因为 ? 前面的系数对时间复 杂度没有影响。 3. 循环嵌套时使用乘法。总操作数量等于外层循环和内层循环操作数量之积,每一层循环依然可以分别 套用第 1. 点和第 2. 点的技巧。 给定一个函数,我们可以用上述技巧来统计操作数量。 void algorithm(int0 码力 | 377 页 | 30.69 MB | 1 年前3
 Hello 算法 1.0.0 C++版return res; } 总的来说,for 循环的代码更加紧凑,while 循环更加灵活,两者都可以实现迭代结构。选择使用哪一个应该 根据特定问题的需求来决定。 3. 嵌套循环 我们可以在一个循环结构内嵌套另一个循环结构,下面以 for 循环为例: // === File: iteration.cpp === /* 双层 for 循环 */ string nestedForLoop(int } } return res.str(); } 图 2‑2 是该嵌套循环的流程框图。 第 2 章 复杂度分析 hello‑algo.com 22 图 2‑2 嵌套循环的流程框图 在这种情况下,函数的操作数量与 ?2 成正比,或者说算法运行时间和输入数据大小 ? 成“平方关系”。 我们可以继续添加嵌套循环,每一次嵌套都是一次“升维”,将会使时间复杂度提高至“立方关系”“四次方 关系”,以此类推。 中的常数项。因为它们都与 ? 无关,所以对时间复杂度不产生影响。 2. 省略所有系数。例如,循环 2? 次、5? + 1 次等,都可以简化记为 ? 次,因为 ? 前面的系数对时间复 杂度没有影响。 3. 循环嵌套时使用乘法。总操作数量等于外层循环和内层循环操作数量之积,每一层循环依然可以分别 套用第 1. 点和第 2. 点的技巧。 给定一个函数,我们可以用上述技巧来统计操作数量: void algorithm(int0 码力 | 378 页 | 17.59 MB | 1 年前3 Hello 算法 1.0.0 C++版return res; } 总的来说,for 循环的代码更加紧凑,while 循环更加灵活,两者都可以实现迭代结构。选择使用哪一个应该 根据特定问题的需求来决定。 3. 嵌套循环 我们可以在一个循环结构内嵌套另一个循环结构,下面以 for 循环为例: // === File: iteration.cpp === /* 双层 for 循环 */ string nestedForLoop(int } } return res.str(); } 图 2‑2 是该嵌套循环的流程框图。 第 2 章 复杂度分析 hello‑algo.com 22 图 2‑2 嵌套循环的流程框图 在这种情况下,函数的操作数量与 ?2 成正比,或者说算法运行时间和输入数据大小 ? 成“平方关系”。 我们可以继续添加嵌套循环,每一次嵌套都是一次“升维”,将会使时间复杂度提高至“立方关系”“四次方 关系”,以此类推。 中的常数项。因为它们都与 ? 无关,所以对时间复杂度不产生影响。 2. 省略所有系数。例如,循环 2? 次、5? + 1 次等,都可以简化记为 ? 次,因为 ? 前面的系数对时间复 杂度没有影响。 3. 循环嵌套时使用乘法。总操作数量等于外层循环和内层循环操作数量之积,每一层循环依然可以分别 套用第 1. 点和第 2. 点的技巧。 给定一个函数,我们可以用上述技巧来统计操作数量: void algorithm(int0 码力 | 378 页 | 17.59 MB | 1 年前3
 Hello 算法 1.2.0 简体中文 C++ 版return res; } 总的来说,for 循环的代码更加紧凑,while 循环更加灵活,两者都可以实现迭代结构。选择使用哪一个应该 根据特定问题的需求来决定。 3. 嵌套循环 我们可以在一个循环结构内嵌套另一个循环结构,下面以 for 循环为例: // === File: iteration.cpp === /* 双层 for 循环 */ string nestedForLoop(int } return res.str(); } 图 2‑2 是该嵌套循环的流程框图。 第 2 章 复杂度分析 www.hello‑algo.com 22 图 2‑2 嵌套循环的流程框图 在这种情况下,函数的操作数量与 ?2 成正比,或者说算法运行时间和输入数据大小 ? 成“平方关系”。 我们可以继续添加嵌套循环,每一次嵌套都是一次“升维”,将会使时间复杂度提高至“立方关系”“四次方 关系”,以此类推。 中的常数项。因为它们都与 ? 无关,所以对时间复杂度不产生影响。 2. 省略所有系数。例如,循环 2? 次、5? + 1 次等,都可以简化记为 ? 次,因为 ? 前面的系数对时间复 杂度没有影响。 3. 循环嵌套时使用乘法。总操作数量等于外层循环和内层循环操作数量之积,每一层循环依然可以分别 套用第 1. 点和第 2. 点的技巧。 给定一个函数,我们可以用上述技巧来统计操作数量: void algorithm(int0 码力 | 379 页 | 18.48 MB | 10 月前3 Hello 算法 1.2.0 简体中文 C++ 版return res; } 总的来说,for 循环的代码更加紧凑,while 循环更加灵活,两者都可以实现迭代结构。选择使用哪一个应该 根据特定问题的需求来决定。 3. 嵌套循环 我们可以在一个循环结构内嵌套另一个循环结构,下面以 for 循环为例: // === File: iteration.cpp === /* 双层 for 循环 */ string nestedForLoop(int } return res.str(); } 图 2‑2 是该嵌套循环的流程框图。 第 2 章 复杂度分析 www.hello‑algo.com 22 图 2‑2 嵌套循环的流程框图 在这种情况下,函数的操作数量与 ?2 成正比,或者说算法运行时间和输入数据大小 ? 成“平方关系”。 我们可以继续添加嵌套循环,每一次嵌套都是一次“升维”,将会使时间复杂度提高至“立方关系”“四次方 关系”,以此类推。 中的常数项。因为它们都与 ? 无关,所以对时间复杂度不产生影响。 2. 省略所有系数。例如,循环 2? 次、5? + 1 次等,都可以简化记为 ? 次,因为 ? 前面的系数对时间复 杂度没有影响。 3. 循环嵌套时使用乘法。总操作数量等于外层循环和内层循环操作数量之积,每一层循环依然可以分别 套用第 1. 点和第 2. 点的技巧。 给定一个函数,我们可以用上述技巧来统计操作数量: void algorithm(int0 码力 | 379 页 | 18.48 MB | 10 月前3
 Hello 算法 1.0.0b1 C++版) 中的常数项,对时间复杂度不产生影响。 2. 省略所有系数。例如,循环 2? 次、5? + 1 次、⋯⋯,都可以化简记为 ? 次,因为 ? 前面的系数对时间 复杂度也不产生影响。 3. 循环嵌套时使用乘法。总操作数量等于外层循环和内层循环操作数量之积,每一层循环依然可以分别套 用上述 1. 和 2. 技巧。 以下示例展示了使用上述技巧前、后的统计结果。 ?(?) = 2?(? + 1) 循环次数与数组长度成正比 for (int num : nums) { count++; } return count; } 平方阶 ?(?2) 平方阶的操作数量相对输入数据大小成平方级别增长。平方阶常出现于嵌套循环,外层循环和内层循环都为 ?(?) ,总体为 ?(?2) 。 // === File: time_complexity.cpp === /* 平方阶 */ int quadratic(int logRecur(float n) { if (n <= 1) return 0; return logRecur(n / 2) + 1; } 线性对数阶 ?(? log ?) 线性对数阶常出现于嵌套循环中,两层循环的时间复杂度分别为 ?(log ?) 和 ?(?) 。 主流排序算法的时间复杂度都是 ?(? log ?) ,例如快速排序、归并排序、堆排序等。 2. 复杂度分析 hello‑algo0 码力 | 187 页 | 14.71 MB | 1 年前3 Hello 算法 1.0.0b1 C++版) 中的常数项,对时间复杂度不产生影响。 2. 省略所有系数。例如,循环 2? 次、5? + 1 次、⋯⋯,都可以化简记为 ? 次,因为 ? 前面的系数对时间 复杂度也不产生影响。 3. 循环嵌套时使用乘法。总操作数量等于外层循环和内层循环操作数量之积,每一层循环依然可以分别套 用上述 1. 和 2. 技巧。 以下示例展示了使用上述技巧前、后的统计结果。 ?(?) = 2?(? + 1) 循环次数与数组长度成正比 for (int num : nums) { count++; } return count; } 平方阶 ?(?2) 平方阶的操作数量相对输入数据大小成平方级别增长。平方阶常出现于嵌套循环,外层循环和内层循环都为 ?(?) ,总体为 ?(?2) 。 // === File: time_complexity.cpp === /* 平方阶 */ int quadratic(int logRecur(float n) { if (n <= 1) return 0; return logRecur(n / 2) + 1; } 线性对数阶 ?(? log ?) 线性对数阶常出现于嵌套循环中,两层循环的时间复杂度分别为 ?(log ?) 和 ?(?) 。 主流排序算法的时间复杂度都是 ?(? log ?) ,例如快速排序、归并排序、堆排序等。 2. 复杂度分析 hello‑algo0 码力 | 187 页 | 14.71 MB | 1 年前3
 Hello 算法 1.0.0b2 C++版) 中的常数项,对时间复杂度不产生影响。 2. 省略所有系数。例如,循环 2? 次、5? + 1 次、⋯⋯,都可以化简记为 ? 次,因为 ? 前面的系数对时间 复杂度也不产生影响。 3. 循环嵌套时使用乘法。总操作数量等于外层循环和内层循环操作数量之积,每一层循环依然可以分别套 用上述 1. 和 2. 技巧。 以下示例展示了使用上述技巧前、后的统计结果。 ?(?) = 2?(? + 1) 循环次数与数组长度成正比 for (int num : nums) { count++; } return count; } 平方阶 ?(?2) 平方阶的操作数量相对输入数据大小成平方级别增长。平方阶常出现于嵌套循环,外层循环和内层循环都为 ?(?) ,总体为 ?(?2) 。 // === File: time_complexity.cpp === /* 平方阶 */ int quadratic(int logRecur(float n) { if (n <= 1) return 0; return logRecur(n / 2) + 1; } 线性对数阶 ?(? log ?) 线性对数阶常出现于嵌套循环中,两层循环的时间复杂度分别为 ?(log ?) 和 ?(?) 。 主流排序算法的时间复杂度都是 ?(? log ?) ,例如快速排序、归并排序、堆排序等。 2. 复杂度分析 hello‑algo0 码力 | 197 页 | 15.72 MB | 1 年前3 Hello 算法 1.0.0b2 C++版) 中的常数项,对时间复杂度不产生影响。 2. 省略所有系数。例如,循环 2? 次、5? + 1 次、⋯⋯,都可以化简记为 ? 次,因为 ? 前面的系数对时间 复杂度也不产生影响。 3. 循环嵌套时使用乘法。总操作数量等于外层循环和内层循环操作数量之积,每一层循环依然可以分别套 用上述 1. 和 2. 技巧。 以下示例展示了使用上述技巧前、后的统计结果。 ?(?) = 2?(? + 1) 循环次数与数组长度成正比 for (int num : nums) { count++; } return count; } 平方阶 ?(?2) 平方阶的操作数量相对输入数据大小成平方级别增长。平方阶常出现于嵌套循环,外层循环和内层循环都为 ?(?) ,总体为 ?(?2) 。 // === File: time_complexity.cpp === /* 平方阶 */ int quadratic(int logRecur(float n) { if (n <= 1) return 0; return logRecur(n / 2) + 1; } 线性对数阶 ?(? log ?) 线性对数阶常出现于嵌套循环中,两层循环的时间复杂度分别为 ?(log ?) 和 ?(?) 。 主流排序算法的时间复杂度都是 ?(? log ?) ,例如快速排序、归并排序、堆排序等。 2. 复杂度分析 hello‑algo0 码力 | 197 页 | 15.72 MB | 1 年前3
 C++高性能并行编程与优化 -  课件 - 16 现代 CMake 模块化项目管理指南src/test/main.cpp ( × ) • GLOB_RECURSE : src/main.cpp (√) src/test/main.cpp (√) • 区别在于 GLOB_RECURSE 允许 * 匹配嵌套的目录。 • 疑问 2 :加了 CONFIGURE_DEPENDS 这个选项有什么区别? • 如果不加,在你创建新文件时, myvar 不会自动更新,还是旧的那几个文件,可能出现 undefined0 码力 | 56 页 | 6.87 MB | 1 年前3 C++高性能并行编程与优化 -  课件 - 16 现代 CMake 模块化项目管理指南src/test/main.cpp ( × ) • GLOB_RECURSE : src/main.cpp (√) src/test/main.cpp (√) • 区别在于 GLOB_RECURSE 允许 * 匹配嵌套的目录。 • 疑问 2 :加了 CONFIGURE_DEPENDS 这个选项有什么区别? • 如果不加,在你创建新文件时, myvar 不会自动更新,还是旧的那几个文件,可能出现 undefined0 码力 | 56 页 | 6.87 MB | 1 年前3
共 14 条
- 1
- 2













