C++高性能并行编程与优化 - 课件 - 07 深入浅出访存优化为什么要 4KB ?原来现在操作系统管理内存是用分页 ( page ),程序的内存是一页一页贴在地址空间中的, 有些地方可能不可访问,或者还没有分配,则把这个页设 为不可用状态,访问他就会出错,进入内核模式。 • 因此硬件出于安全,预取不能跨越页边界,否则可能会触 发不必要的 page fault 。所以我们选用页的大小,因为本 来就不能跨页顺序预取,所以被我们切断掉也无所谓。 • 另外,我们可以用 malloc 时,操作系统并不会实际分配那一块内存,而是将这一段内存标记 为“不可用”。当用户试图访问(写入)这一片内存时,硬件就会触发所谓的缺页中断 ( page fault ),进入操作系统内核,内核会查找当前进程的 malloc 历史记录。如果发 现用户写入的地址是他曾经 malloc 过的地址区间,则执行实际的内存分配,并标记该段 内存为“可用”,下次访问就不会再产生缺页中断了;而如果用户写入的地址根本不是他 初始化数组时,内存被写入,所以操作系统这时候才开始实际分配内存。 • 刚才的案例里,不会初始化的 malloc ,第一次往里面赋值时,因为这时操作系统还没有给 这个数组分配内存,所以会触发缺页中断,进入操作系统内核给数组分配内存,是内核执 行内存分配的这个动作,花费了额外的时间。而第二次因为内存已经被分配上了,所以再 次访问也不会触发缺页中断,所以看起来比第一次快很多。 进一步:分配是按页面( 4KB )来管理的 •0 码力 | 147 页 | 18.88 MB | 1 年前3
Rust分布式账务系统 - 胡宇● 事务层与账户层分 离 ● 独立水平扩展 ● CQRS ● Event Sourcing ● 针对读场景,写场 景分别优化 ● 稳定的底层 API ● 灵活的顶层 API ● 树状结构 ● 聚合查询 ● 正确性:内存安全,线程安全 ● 可靠性: Raft 共识算法 raft-rs ● 高性能:关键路径无锁单线程 顶层架构 ● Gateway 路由层 ○ 业务 Marker 事务层 ○ 使用业务 id 进行路由 ○ 执行转账计划 ○ 分发账户变动请求 ● Auticuro 账户层 ○ 使用账户 id 进行分区 ○ 执行账户变动请求 ○ 更新账户余额 分布式账务系统 性能展示 8 vCPUs * 5 节点 SSD 磁盘 当 TPS = 10K 时, 延迟 P99 < 20ms 分布式账务系统 高吞吐,超低延迟 账户层: Auticuro Auticuro 分布式账务系统 账户层: Auticuro ● 1. 接受转账请求,转换成 events ○ Tokio + Tonic 分布式账务系统 1 2 3 4 ● 1. 接受转账请求,转换成 events ● 2. 将 events 送入 Raft 共识,等待 events 被多数节点保存 ○ 共识:基于 raft-rs 的可靠消息队 列 ○ 存储: Rocksdb0 码力 | 27 页 | 12.60 MB | 1 年前3
C++高性能并行编程与优化 - 课件 - 10 从稀疏数据结构到量化数据类型Concurrency in Action )不一定就是完美解决方案,要根据实际情况判断。 真正的解决: tbb::spin_mutex 其实主要的瓶颈在于 std::mutex 会切换到操作系统内核中去调度 ,非常低效。而 tbb::spin_mutex 则是基于硬件原子指令的,完全 用户态的实现。区别: std::mutex 的陷入等待会让操作系统挂起 该线程,以切换到另一个;而 tbb::spin_mutex parallel for collapse(2) 遍历二维区间。 把 func 捕获为 firstprivate ,从而支持用 lambda 捕获的访问者模式。 实现访问者模式 • 额,总之就是每一层都有一个缓存。 第 5 章:量化整型 使用 int :每个占据 4 字节 • 记得我第七课说过,一个简单的循环体往 往会导致内存成为瓶颈( memory- bound )。 • 右边就是一个很好的例子。 malloc 时,操作系统并不会实际分配那一块内存,而是将这一段内存标记为“不可 用”。当用户试图访问(写入)这一片内存时,硬件就会触发所谓的缺页中断( page fault ),进入操作系统内核,内核会查找当前进程的 malloc 历史记录。如果发现用户写 入的地址是他曾经 malloc 过的地址区间,则执行实际的内存分配,并标记该段内存为“可 用”,下次访问就不会再产生缺页中断了;而如果用户写入的地址根本不是他0 码力 | 102 页 | 9.50 MB | 1 年前3
C++高性能并行编程与优化 - 课件 - 09 CUDA C++ 流体仿真实战的三维数组来表示。 • 定义求散度的核函数,首先读取速度场周围六个元素的值,然后上下做差得到散度。 投影部分: jacobi 迭代求解压强 投影部分:速度减去压强的梯度 投影部分:初步实现 • 现在调用这些内核,让 jacobi 迭代 400 次,看看效果。 • 当然, jacobi 迭代因为需要写入 pre 的同时读取 pre ,所以也要用双缓冲。 投影部分:计算未消除的散度 为了评估效果的好坏, :分离实现 CMake :使用 CUDA 编译器,链接 OpenVDB 在 Blender 中查看导出的结果 边界条件 边界条件:初始化 边界条件:添加判断边界的版本 边界条件:仅在第一层额外判断边界条件 进一步改进 VDB 导出:支持导出多个网格,并指定名称 进一步改进 VDB 导出: P-IMPL 模式 进一步改进 VDB 导出: F-IMPL 模式 Blender 渲染结果0 码力 | 58 页 | 14.90 MB | 1 年前3
C++高性能并行编程与优化 - 课件 - 05 C++11 开始的多线程编程暴力解决:用 mutex 上锁 • 这样的确可以防止多个线程同时修改 counter 变量,从而不会冲突。 • 问题: mutex 太过重量级,他会让线程被 挂起,从而需要通过系统调用,进入内核 层,调度到其他线程执行,有很大的开销 。 • 可我们只是想要修改一个小小的 int 变量 而已,用昂贵的 mutex 严重影响了效率 。 建议用 atomic :有专门的硬件指令加持 •0 码力 | 79 页 | 14.11 MB | 1 年前3
C++高性能并行编程与优化 - 课件 - 性能优化之无分支编程 Branchless Programming关键字给类定义一个虚函数,他其实就是在类成员里加了一个函数指针。 • 而在构造函数里,会把当前类重载过的虚函数,赋予给那个函数指针,实现多态。 • 虚函数是 C++ 的语法糖,纯 C 的 Linux 内核中也用到多态,就是用函数指针实现的。 例如左图中的虚函数,和右边的函数指针版本等价。因此性能分析时,把虚函数视为函数 指针。 • 注:实际中虚函数往往有很多个,为了存储空间的高效利用,会把多个虚函数打包成一个数组,称之0 码力 | 47 页 | 8.45 MB | 1 年前3
新一代分布式高性能图数据库的构建 - 沈游人AtlasGraph 架构及实现 新一代图技术应用特征简介 Takeaway AtlasGraph 架构概览 存储层 副本管理 CRAQ 图原生存储 索引 LSM-Tree 容灾保障 ( BR ) 元数据层 事务管理 MVOCC 计算层 Cypher AST 优化器 图计算 内存加速引 擎 服务接口 HTTP/RPC Spark Spark 连接器 Python UDF 执行器 索引管理 一致性存储 RAFT 分片管理 元数据 集群管理 用户权限 GNN 应用层 Atlas 图平台 Atlas Studio Atlas Client 基础 设施 Docker/K8S/VM X86/ARM - 基于 RUST 语言保证性能优势 - 分布式架构性能可线性扩展 - 针对大规模图优化的存算引擎0 码力 | 38 页 | 24.68 MB | 1 年前3
C++高性能并行编程与优化 - 课件 - 17 由浅入深学习 map 容器个元素的平衡二叉树,深 度只有 ceil(log(n+1)) 层。也就是说我们最多只需要 ceil(log(n+1)) 次大小判断,就能找到任 意一个数!因为算法复杂度可以忽略 +1 -1 这些小东西,所以 set 查找的最坏复杂度是 O(logn) ! 2 1 4 5 8 7 4 要找的数 ceil(log(6+1)) = 3 层 4 < ? 从 set 到 map :无非是外挂了个值类型 3 次就找到了目标。这还是最坏的情况,最好只需要 1 次就够了。 • 最坏的情况需要判断多少次?最坏不会超过树的深度,而一棵有着 n 个元素的平衡二叉树,深 度只有 ceil(log(n+1)) 层。也就是说我们最多只需要 ceil(log(n+1)) 次大小判断,就能找到任 意一个数!因为算法复杂度可以忽略 +1 -1 这些小东西,所以 set 查找的最坏复杂度是 O(1) ! 4 要找的数0 码力 | 90 页 | 8.76 MB | 1 年前3
C++高性能并行编程与优化 - 课件 - 06 TBB 开启的并行编程之旅任务,一个负责下载,一个负责和用户交 互。并在主线程中等待该任务组里的任务 全部执行完毕。 • 区别在于,一个任务不一定对应一个线程 ,如果任务数量超过 CPU 最大的线程数, 会由 TBB 在用户层负责调度任务运行在 多个预先分配好的线程,而不是由操作系 统负责调度线程运行在多个物理核心。 封装好了: parallel_invoke 更好的例子 第 1 章:并行循环 时间复杂度( time-efficiency auto_partitioner 快 3.31 倍 原因 • tbb::simple_partitioner 能够按照给定的粒度 大小( grain )将矩阵进行分块。块内部小区 域按照常规的两层循环访问以便矢量化,块外 部大区域则以类似 Z 字型的曲线遍历,这样 能保证每次访问的数据在地址上比较靠近,并 且都是最近访问过的,从而已经在缓存里可以 直接读写,避免了从主内存读写的超高延迟。0 码力 | 116 页 | 15.85 MB | 1 年前3
C++高性能并行编程与优化 - 课件 - 11 现代 CMake 进阶指南进一步求值: if (“world” MATCHES “Hello”) 从而会执行假分支,结果不正常了。 解决:用引号包裹起来,防止被当做变量名 初学者如果搞不明白,可以把所有不确定的地方都套上一层引号, 例如” ${MYVAR}” ,这样就可以避免被 if 当做变量名来求值了。 第 10 章:变量与作用域 变量的传播规则:父会传给子 • 父模块里定义的变量,会传递给子模块。 变量的传播规则:子不传给父 • 如果父模块里本来就定义了同名变量,则离开子模块后仍保持父模块原来设置的值。 如果子模块需要向父模块里传变量怎么办? • 可以用 set 的 PARENT_SCOPE 选项,把一个变量传递到上一层作用域(也就是父模 块)。 如果子模块需要向父模块里传变量怎么办? • 如果父模块里没有定义 MYVAR 的话,也可以用缓存变量向外部传变量(不推荐)。但是 这样就不光父模块可见了,父模块的父模块,到处都可见。0 码力 | 166 页 | 6.54 MB | 1 年前3
共 16 条
- 1
- 2













