PGAS并行编程模型全解析:全局地址空间与单边通信实战
发布时间:2026/9/9 13:10:37 锦皓数字建站

写并行程序这些年MPI 和 OpenMP 基本是绕不开的两座大山。MPI 在分布式集群上确实能打但那种“通信双方必须同时配合”的 send/recv 编程真心费劲尤其是通信模式一复杂死锁调试能熬到你怀疑人生OpenMP 在单机多核上舒服一个指令并行就完了可一旦程序要跨节点跑基本就使不上劲了。后来我在一个分布式图计算项目里反复折腾数据布局和通信逻辑才真正把 PGASPartitioned Global Address Space分区全局地址空间这套模型吃透算是打开了一个新世界。这篇就把 PGAS 模型从头到尾拆开聊一遍包括它到底解决了什么问题、核心概念、主流语言和库、实操经验以及我踩过的一些坑。想搞大规模科学计算、分布式训练或者图算法的同学这篇应该能帮你省不少时间。1. 为什么还需要一种新的并行编程模型1.1 消息传递和共享内存各自的“天花板”先回到最熟悉的两个老家伙。MPI 属于消息传递模型程序里每个进程有自己的独立地址空间进程之间要交换数据必须显式地调用 send 和 recv。这个模型很通用几乎什么分布式硬件都能跑但问题是通信逻辑完全靠程序员自己管。举个例子你想让进程 0 把一个数组发给进程 1两边必须一前一后调用匹配的发送和接收接口还得注意缓冲区大小、通信顺序一不小心就死锁。我早期写过一段管道式的 MPI 程序因为消息顺序没配对好整个集群的进程全卡在 wait 上最后只能靠超时机制一个一个排查。OpenMP 走的则是相反的路子它假设内存是共享的多个线程可以直接读写同一个变量硬件层的缓存一致性协议帮你处理了绝大多数同步问题。这种模型在单机上确实爽写起来跟串行程序差别不大但放到分布式环境下就失效了。你没法让两个物理节点的 CPU 共享同一块内存除非搞分布式共享内存件而这类方案在大规模集群上的性能普遍不理想网络延迟和一致性开销会让你怀疑人生。这两个模型各有一个鲜明的优势也各有一个明显的天花板。MPI 的优势是显式数据分布每个进程都知道自己手里有什么扩展性好代价是通信代码极其繁琐。OpenMP 的优势是全局视角数据分布和通信都不用管代价是基本没法跨节点。那自然会有人想有没有一种模型既能像 OpenMP 一样给程序员一个全局的地址空间又能像 MPI 一样明确数据物理上落在哪个节点让性能可预测1.2 PGAS 的定位两个世界的交叉点PGAS 就是冲着这个需求出来的。它的完整名字叫 Partitioned Global Address Space翻译过来就是“分区全局地址空间”。理解这个词组就够了全局地址空间意思是程序里所有处理单元Processing Element简称 PE能看到一个统一的共享内存视图你可以像访问本地变量一样去访问其他 PE 上的数据分区意思是在物理上这个全局地址空间是被拆成一块一块的每一块归属于某一个 PE归属它的数据就存放在那个 PE 的本地内存里。这个设计最妙的地方是保留了“亲和性”这个概念。数据虽然在逻辑上是全局的但每个 PE 访问自己本地的数据肯定比访问远程节点快得多访问延迟可能差出一两个数量级。所以 PGAS 程序写得好不好很大程度上取决于你有没有利用好数据的局部性。这跟 NUMA 架构下写高性能程序的思路很像不是所有内存访问都一样快你得知道哪些是本地访问、哪些是远程访问然后尽量让计算发生在数据所在的地方。PGAS 的价值在于它比 MPI 更易于表达复杂的数据共享关系又比 OpenMP 具备更强的分布式扩展能力。对于大数据、图计算、稀疏线性代数这些“分布式数据结构”特别复杂的场景PGAS 能让代码简洁很多同时性能上不会因为过于抽象而损失太多。2. PGAS 的核心技术点逐个拆解2.1 分区与全局编址一个数组是怎么散到整个集群的要理解 PGAS先看它的全局编址方式。假设你声明了一个共享数组shared int data[N]在 PGAS 语言里这个数组的空间会按照一定的分布规则被切分到各个 PE 上。最简单的分布是“块分布”比如N 1024PE 4那么默认情况下 PE0 拥有前 256 个元素PE1 拥有接下来 256 个以此类推。访问data[i]的时候编译器会根据 i 值计算出这个元素归哪个 PE 管然后自动决定是本地访问还是远程访问。这种编址方式给编程带来的直接好处是你不用手动调用通信函数去请求数据而是像读普通数组一样读写共享数组后台会自动帮你生成通信操作。这就省掉了 MPI 里大量显式的消息传递代码。但要注意这个便利是有代价的——编译器帮你掩盖了潜在的远程访问如果你没意识到这一点很容易写出大量隐式通信性能直接崩掉。写 PGAS 代码的时候时刻要问自己一句这个操作是本地访问还是远程访问远程访问的代价大不大分布方式不只是默认的块分布。很多 PGAS 语言支持 cyclic 分布也就是轮流分配元素比如 4 个 PE元素 0 给 PE0元素 1 给 PE1元素 2 给 PE2元素 3 给 PE3元素 4 又回到 PE0。块分布适合负载比较规整的场景循环分布适合需要对数组做并行访问、同时希望负载更均衡的场景。还有更通用的 block-cyclic 分布就是把数组先切成若干个块再按循环方式把块分给 PE。选对分布方式直接影响程序的通信量和负载均衡。2.2 单边通信为什么它比 send/recv 更省心PGAS 的核心通信原语是单边通信也就是 put 和 get。所谓 put就是直接把数据写到远端某个 PE 的地址上get就是从远端读数据到本地。整个过程的发起者是单个 PE远端 PE 不需要像在 MPI 里那样配合执行一个 recv 操作。这个“不需要配合”就是单边通信最核心的价值所在。想象一下 MPI 版本的远程内存写入进程 A 要发一段数据给进程 BB 必须先调 recv 或达到某个 MPI_Irecv 状态否则 A 的 send 可能阻塞或者数据堆积在系统缓冲区里。但在 PGAS 里A 直接执行 put 把数据放到 B 的某个变量里B 完全不需要知道发生了什么。这在实现动态负载均衡、工作窃取、流水线算法这些场景时特别有效因为接收方不用预先安排好通信时序。单边通信之所以能高性能实现很大程度上依赖现代高速网络的 RDMA 能力。InfiniBand、RoCE 这些网络硬件允许网卡绕过远端 CPU直接读写远端内存这就让 put/get 做到了真正意义上“旁路”接收方。PGAS 的运行时库通常都会在底层选一个通信抽象层比如 GASNet 或 UCX把它们封装好向上提供 put/get 接口。所以你在 PGAS 程序里写一个 put最终大概率会落到一次 RDMA PUT 操作上性能相当可观。2.3 同步与一致性看不见的排序约束单边通信很爽但同步模型必须搞明白否则就会出现“数据明明写了别人却读到旧值”的诡异问题。PGAS 的一致性模型通常比 OpenMP 要弱换句话说普通 put/get 操作和本地内存访问之间可能会被重排跨 PE 的可见性也不是即刻保证的。如果你想让远端 PE 看到你之前所有 put 写过去的数据必须显式执行 fence 或 flush 类的操作。这种弱一致性模型听起来麻烦但其实是取舍的结果如果每个 put 都立即对所有 PE 可见那每次 put 都要做全局同步性能就无法接受了。所以 PGAS 把优化空间留给你让你在必要的时刻才做同步。这有点像 C 内存模型里的 relaxed ordering 和 acquire/release平时各写各的需要跨线程通信的时候才加同步。另一个经常用到的同步工具是 barrier也就是全局屏障确保所有 PE 都到达某个点之后才继续往下走。很多算法里一次迭代结束、下一步迭代开始之前都需要一次 barrier 来保证数据完整。但 barrier 的开销会随着 PE 数量增加而变大所以如果你发现程序整体性能瓶颈在同步等待上就该考虑减少 barrier 频次、改用点对点的弱同步或原子操作来替代。2.4 亲和性与性能知道你的数据在哪儿比什么都重要PGAS 跟一般共享内存模型最大的区别就是它时刻提醒你地址虽然是全局的但物理位置是有远近之分的。在 OpenMP 下你不关心某个变量在哪因为所有线程访问内存的时间差不多在 PGAS 下如果你频繁远程读数据延迟和带宽都会很难看。所以写 PGAS 代码的核心策略是“数据放置优先于代码优化”让一个 PE 主要处理它自己拥有的数据远程访问只发生在边界数据交换的环节。举个最常见的例子——并行卷积或模板计算。每个 PE 负责计算自己数组区域的新值但算边界元素时需要邻域数组元素。这些邻域元素可能存储在相邻 PE 上。这时正确做法是先把边界的 ghost cell 用一次批量 put/get 拿到本地然后循环里全程走本地内存而不是每次都远程访问。不熟悉 PGAS 的程序员常常忽略这一点写出来的代码在功能上没问题但在千核规模下性能一塌糊涂。说白了PGAS 把“哪里慢了”的责任交到你手上你得学会跟数据放置打交道。3. 主流 PGAS 语言与实现选型3.1 语言派UPC、Coarray Fortran 与 ChapelPGAS 理念已经催生了一大批编程语言其中比较有代表性的有 UPCUnified Parallel C、Coarray Fortran 和 Chapel。UPC 是 C 语言的 PGAS 扩展它引入shared类型修饰符和全局指针在 C 语言基础上增加了共享数组、远程访问和同步机制。如果你想在 C/C 生态里体验 PGASUPC 是个比较直接的选择但它的编译器与工具链生态相对有限商业支持也不多。Coarray Fortran简称 CAF则是把 PGAS 思想直接引入了 Fortran 标准从 Fortran 2008 开始正式成为语言的一部分。在 Fortran 里声明一个 coarray比如real :: a(10)[*]方括号里的*表示跨 image也就是进程访问a(1)[2]就是访问第 2 个 image 上的a(1)。这种语法非常自然Fortran 本来就有很强的数组语义加了这个扩展之后写分布式数值计算代码非常顺手。很多气候模拟、地球物理程序都在用 CAF因为 Fortran 在这些领域仍然是主力语言。Chapel 则走的是高生产率路线它的目标之一就是让并行编程更接近人类思维方式。Chapel 同时支持任务并行和数据并行PGAS 式的全局地址空间也是内置特性。不过 Chapel 的编译器成熟度和性能调优工具相比 CAF 和底层的 PGAS 库还有差距。如果你是在快速原型验证阶段Chapel 会很舒服要是需要上大规模超算跑关键任务我可能会更倾向于成熟的 CAF 或 OpenSHMEM 方案。3.2 库派OpenSHMEM、Global Arrays 与 MPI-3 RMA不是所有场景都需要一门新语言所以 PGAS 也以库形式存在。OpenSHMEM 是开放标准保留了 SHMEM 的 API 风格提供双向的 put/get、原子操作、集合通信以及对称内存模型。用 OpenSHMEM 的好处是不用换编译器直接在 C/C 或 Fortran 里调用库函数移植性也很好。目前很多超算中心提供的 OpenSHMEM 实现都是经过性能调优的是追求底层性能和可控性的不二选择。Global Arrays简称 GA则以“分布式数组”为核心抽象在化学计算社区如 NWChem中非常流行。它的特点是你可以在全局地址空间里创建多维数组然后自由读写任意区域底层自动生成远程通信。GA 很贴近科学计算里“大规模矩阵/张量”的实际需求学习成本比语言级 PGAS 要低不少。还有一类容易被人忽略的选项是 MPI-3 标准里的 RMARemote Memory Access功能。MPI-3 提供了 one-sided 通信 API可以在 MPI 程序里直接使用 put/get、accumulate 等操作不需要新语言也能在纯 MPI 生态环境里享受部分 PGAS 的优势。但说实话MPI-3 RMA 的 API 设计没有 OpenSHMEM 那么顺手创建窗口、管理 epoch 还是挺繁琐的。如果你已经在 MPI 技术栈上而通信模式又比较简单用 MPI-3 RMA 可以减少依赖但从效率和易用性来看我更喜欢 OpenSHMEM 或真正的 PGAS 语言。3.3 底层通信层GASNet 与 UCX 的作用无论你选哪种 PGAS 语言或库底层几乎都离不开一个高性能通信运行时。常见的包括 GASNet、UCX 和 Libfabric。GASNet 是专门为 PGAS 语言设计的通信中间件标准化了 put/get、集体操作、原子操作等接口支持 InfiniBand、Cray、以太网等多种网络。UCX 则是一套更通用的高速通信框架被很多 MPI 实现和 PGAS 库用作底层传输层。理解这一层对定位性能问题很有帮助。有时候你的 PGAS 程序慢不一定是代码逻辑问题而是通信运行时没有正确匹配硬件比如没有走 RDMA 而走了 TCP或者网络选路配置不当。这种情况下你需要看运行时提供的环境变量和诊断信息确认通信层是否真正利用了硬件加速能力。我遇到过几次类似的情况最后都是通过环境变量检查和网络带宽测试定位到通信层配置问题而不是在应用代码里瞎调参数。4. 一个简化示例PGAS 怎么写出分布式程序4.1 一个数组求和的 OpenSHMEM 示例我写一个 OpenSHMEM 风格的数组求和示例帮助你直观感受 PGAS 的编程模式。这个例子把所有 PE 的共享数组元素求和到 PE0 上但每个 PE 先算自己的局部和再用一次单边 put 把局部和汇总到 PE0 的缓冲区里#include shmem.h #include stdio.h #define N 100000 int main(void) { shmem_init(); int npes shmem_n_pes(); int mype shmem_my_pe(); static long data[N]; static long local_sum 0; // 每个 PE 初始化自己的 data for (int i 0; i N; i) { data[i] mype * 100 i; } // 每个 PE 先计算本地局部和 for (int i 0; i N; i) { local_sum data[i]; } // 在对称堆上申请一个 long 变量 long *global_sum (long *)shmem_malloc(sizeof(long)); *global_sum 0; // 单边原子累加把局部和加到 PE0 的 global_sum 上 if (mype 0) { // PE0 需要等所有 PE 都完成累加再取结果 shmem_sync_all(); printf(total sum %ld\n, *global_sum); } else { shmem_long_atomic_add(global_sum, local_sum, 0); } shmem_sync_all(); shmem_free(global_sum); shmem_finalize(); return 0; }注意上面代码有一个简化错误——如果 PE0 的*global_sum 0发生在其他 PE 的 atomic_add 之后结果就不对了。这里其实需要先在所有 PE 上重置global_sum为 0然后在 barrier 之后做 atomic_add。实际正确写法是把*global_sum 0放在shmem_sync_all()之后然后所有 PE包括 PE0都执行 atomic_add再由 PE0 读取。为了不误导我建议你按下面的顺序处理long *global_sum (long *)shmem_malloc(sizeof(long)); if (mype 0) { *global_sum 0; } shmem_sync_all(); // 确保 global_sum 已清空 shmem_long_atomic_add(global_sum, local_sum, 0); shmem_sync_all(); // 确保累加完成 if (mype 0) { printf(total sum %ld\n, *global_sum); }从这个例子可以看出PGAS 程序里大量逻辑其实和串行代码很像唯一的差异是显式的分布和数据共享。你不需要手写 send/recv但依然需要对同步顺序有全局把握。4.2 与 MPI 版本的直观对比如果把同样的数组求和改写为 MPI 版本最常见做法是每个进程先算局部和然后用 MPI_Reduce 把局部和归约到全局和。代码也不复杂核心就一行MPI_Reduce(local_sum, global_sum, 1, MPI_LONG, MPI_SUM, 0, MPI_COMM_WORLD);。从代码量看MPI 在这个场景甚至更简洁因为 Reduce 是 MPI 的内置操作。这提醒我们PGAS 不是在所有场景都比 MPI 更少代码它的优势更体现在共享数据结构的随机访问和动态通信模式上。假如你要做的是一个分布式哈希表每个进程负责一部分键值对查找或插入时都必须访问正确的目标进程。在 MPI 里你需要为每种键的访问封装一套 send/recv 协议处理请求队列和应答匹配在 PGAS 里你可以把表直接建成一个共享结构通过单边 put/get 完成读改写。在这种“远程数据操作密集”的场景PGAS 的代码量和架构复杂度优势才会充分体现。4.3 这段代码里隐藏的成本上面这段求和代码里最大的隐藏成本是shmem_long_atomic_add(global_sum, local_sum, 0)这一行。它每次调用都会产生一次原子操作虽然开销比普通 put 高一些但由于只有一个 int 长度的数据延迟大概就是一次 RDMA atomic 的延迟。如果总共只有几个 PE 这么操作微不足道但如果你在一个大循环里对同一个远端变量做了几百万次 atomic_add那性能就会非常难看。这时候就必须考虑批量合并。比如先让每个 PE 计算完整批量部分结果再一次性调用原子加操作或者在每个 PE 本地维护一个累积数组最后统一同步。我在实际项目里的原则是循环内尽量避免任何远程操作就算需要远程更新也想办法攒一批一次性发送。这是所有 PGAS 性能优化的核心心法。5. 实战中的性能调优经验5.1 先想清楚“数据放哪”再写第一行代码很多同学拿到 PGAS 就开始写代码觉得共享数组想用就用结果运行效率常常不如 MPI。原因很简单编译器无法替你判断数据放置策略它只知道按默认规则分布。而默认的块分布适合数组顺序访问却不适合所有访问模式。比如你的算法是每个 PE 需要频繁访问数组里某个固定偏移的跨区数据那么默认块分布可能造成大量远程访问。正确流程是先在纸上把整个算法的数据访问模式列出来标出哪些数据会被哪些 PE 反复访问然后据此决定分布策略。能本地算完的过程尽量本地算需要全局共享的动态结构用 PGAS 的共享数组或模型来表达但访问边界一定要用批量接口去同步。有一句话我经常跟团队说数据分布就是 PGAS 程序里的“架构设计”这一步错了后面怎么调都只能小修小补。5.2 批量通信把远程小消息“攒成大包”网络通信对包大小非常敏感。发送一个 8 字节的 put 和发送一个 4KB 的 put延迟可能相差不大但吞吐量天差地别。小消息的开销主要花在握手、校验、DMA 队列处理等协议成本上而大消息则能把带宽利用起来。因此如果程序里频繁访问远端内存的分散小区域最直接的方法是在本地建一个缓冲区批量复制到缓冲区再一次性 put 到远端。这个优化有些像网络编程里的“合并小包发送”。比如在做分布式稀疏矩阵向量乘的时候相邻 PE 之间要交换很多边界值每个值可能就几个字节。如果你每个值都调一次 put通信量巨大如果先把整块 ghost row 打包成连续内存用一次 put 发过去时间立刻降一个数量级。大多数 PGAS 运行时的手册都会强调批量操作但真正被大量代码忽略的恰恰是这一点。5.3 同步粒度能粗就别细PGAS 提供了丰富的同步机制barrier、fence、原子操作、point-to-point sync。初学者容易犯的错误是过度使用全局 barrier。每加一次 barrier所有 PE 都得停下等待最慢的那个扩展到几千个 PE 时这个等待时间会被放大得非常明显。更合理的做法是识别算法中真正依赖全局状态的点尽量减少全局 barrier 的使用。对于流水线型计算每两个相邻阶段之间只需要局部同步让数据流自然前进对于生产者-消费者模式可以使用带计数器更新的原子操作来代替全同步。你会发现自己写的程序变得更像是一个分布式系统而不是一个由统一时钟驱动的大循环这种设计在高基数扩展时优势十分突出。5.4 用 profile 工具定位性能瓶颈调优不能靠猜必须量化。性能分析工具能看到远程访问次数、通信耗时、同步等待时间等信息。如果是 OpenSHMEM 或基于 GASNet 的程序通常可以使用 perf 工具、TAU、HPCToolkit 之类。我自己会先做一个小规模的 scalability 测试1 个 PE、2 个 PE、4 个 PE...观察扩展曲线如果性能没有线性扩展多半是通信没有收敛。然后再给代码打点统计每个 PE 的远程 put/get 总次数和总字节数。如果一个循环里有大量远程访问profile 结果会显示某行代码耗时异常高。这时候回到代码去看看能不能通过数据重排、批量发送或换种同步方式把它优化掉。不要凭感觉用数据说话这绝对是排障最靠谱的路线。6. 常见问题与“踩坑”实录6.1 没有死锁但有数据竞争PGAS 不是安全伞以前我做 MPI 习惯了一听到 PGAS 说“不需要接收方参与”第一反应是太好了。但可怕的事情发生了——程序没有死锁但结果偶尔正确偶尔错。后来排查发现多 PE 同时对同一个共享变量做 put顺序不确定最终值取决于谁后到这本质上就是一个数据竞争问题。解决方案也很直接对共享变量的写操作要么放到同步区域内要么使用原子操作。不要因为 PGAS 把底层通信藏起来了就忽略了并发写的安全性。其实 PGAS 对数据竞争的约束和 OpenMP 一样严格只是隐藏得更深反而更容易被忽略。我建议在任何写共享变量的代码块前后都明确加上同步或原子操作避免“凭运气”得到正确结果。6.2 性能反而比 MPI 还差多半是放置和访问模式的问题我在一个矩阵计算项目里尝试用 PGAS 重写核心模块第一次跑完直接傻眼——比原来的 MPI 版本慢了接近一倍。仔细分析后发现问题出在两点一是共享数组用了默认分布而算法的主循环需要跨 PE 读取大量数据二是我在一个大循环里写了细粒度的远程 get每次只取一个标量值通信次数爆炸。解决方式就是批量化和数据重排。把远程 get 改成先批量拷到本地缓冲区或者调整分布让主循环全程访问本地数据。调整之后性能不仅追平了 MPI还因为通信逻辑简化在很多小规模数据上更快。这个经历让我深刻体会到PGAS 不是魔法它只是给了你一个更容易组织和表达远程访问的框架该做的通信优化一次都不能少。6.3 可移植性和实现差异不同运行时行为不一样PGAS 语言和库在标准化程度上有高有低。Fortran coarray 是标准的一部分但不同编译器优化的程度和运行时开销差别不小OpenSHMEM 实现也可能在某些硬件上的行为略有差异。如果你的代码要跨多台机器、多套编译器环境运行最好在开始前就做一次兼容性测试避免“在这台机器跑得好换台机器跑不起来”的状况。实际项目里我会尽量把 PGAS 相关的操作封装在一小组内部 API 里这样即使底层实现变化上层逻辑也不用大改。同时要留意文档中的环境变量和运行时参数通常能帮你解决不少“移植后莫名变慢”的问题。6.4 GPU 与异构计算PGAS 的延伸与限制现在大规模并行计算基本绕不开 GPUPGAS 在这方面也在进化。传统的 PGAS 主要面向 CPU 节点但不少新工作已经尝试把 PGAS 扩展到 GPU比如让 GPU kernel 直接通过单边通信访问其他节点的 GPU 显存。不过这类方案对硬件和软件栈的要求很高远没有 CPU 版 PGAS 成熟。如果你想在 GPU 上用到 PGAS 式的全局地址空间思路可以先看看有没有新版的 PGAS 实现支持 GPU或者退而求其次用支持 RDMA 的 GPU 直通技术来规避主机内存中转。异构体系下的 PGAS 还在快速发展期使用前一定要先做最小基准测试不要直接拿整个应用上。7. 给新手的“抄作业”级建议7.1 什么项目适合引入 PGAS不是所有项目都需要 PGAS。如果你的程序通信模式很固定比如规约、广播、定期交换整个数组那么 MPI 已经很成熟没必要换成 PGAS。但如果你的项目里需要频繁访问分布式共享数据结构比如图索引、哈希表、稀疏矩阵或者负载动态变化通信发起方不确定那么 PGAS 会显著降低你的实现复杂度。用一句话判断如果你发现自己经常在 MPI 里写“负责管理某个数据块的服务端循环”那 PGAS 很可能更适合你。7.2 从 MPI 平滑迁移到 PGAS 的路径如果现在有一份完整的 MPI 代码我建议你不要全部推翻重写。可以先抽取几个核心模块换成 PGAS 风格做实验对比性能和代码量。最适合试水的是那些通信逻辑复杂但数据量不大的模块比如负载均衡器、分布式哈希表、动态任务队列。这些模块改成 PGAS 后往往结构会清晰很多性能也不会差。整体迁移完成后再逐步清理那些不再需要的 MPI 通信辅助函数。7.3 我的学习路线与最后一句话如果你想系统掌握 PGAS我的建议是先选一个库派实现比如 OpenSHMEM把 put/get、原子操作、同步这几个基础概念练熟然后再去看一门 PGAS 语言CAF 或 UPC的语法设计理解语言吸收了什么、漏掉了什么。我个人的习惯是如果项目里已经有成熟的 MPI 库我不会为了 PGAS 而 PGAS 去重写如果是从零开始的新项目需要快速表达分布式数据结构PGAS 的收益会非常直观。最后再分享一个小习惯任何 PGAS 程序开工之前我都会先把数据分布画在一张纸上标清每个 PE 存放哪些元素、远程访问的只有哪些边界数据。这张纸比一堆调优技巧都管用。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。