CS267 · HW3 导读

并行基因组组装(UPC++)

用 UPC++ 的 PGAS 模型并行化 de novo 基因组组装——实现分布式哈希表,遍历 k-mer 生成 contigs,体验 one-sided 通信与 RDMA 的力量。

📅 截止:2025-04-11 🎓 对应:Lecture 11 💻 C++ (UPC++) 📂 human-chr14-synthetic.txt

这个 Homework 要你做什么?

一句话总结:使用 UPC++ 并行化 de novo 基因组组装——实现分布式哈希表来存储和遍历 k-mer,从 reads 中重建 contigs。修改 kmer_hash.cpp 和 hash_map.hpp,在 1-8 节点、每节点 60 任务上测试扩展性。

这是你第一次用 PGAS 模型编程。与 MPI 的 two-sided 通信不同,UPC++ 的 rget/rput 让你可以直接读写远程 rank 的内存——无需远端 CPU 参与,硬件 RDMA 让通信与计算重叠。

主要组件

组件对应课程核心概念
kmer_hash.cppLecture 11分布式哈希表、k-mer 存储
hash_map.hppLecture 11PGAS 数据结构、global_ptr
contig 生成Lecture 11k-mer 遍历、图遍历
多节点扩展Lecture 11弱扩展 / 强扩展实验
报告—扩展图 + 优化讨论

前置知识清单

课程知识

UPC++ 编程基础

生物信息学背景


如何开始?

0

理解串行算法

先理解串行版本的基因组组装流程:

  1. 读取所有 reads,提取 k-mer
  2. 将 k-mer 插入哈希表(记录出现次数)
  3. 从高频 k-mer 出发,遍历 de Bruijn 图
  4. 输出 contigs(无分支的路径)

数据集:/global/cfs/cdirs/mp309/cs267-spr2025/hw3-datasets/human-chr14-synthetic.txt(51-mers)

1

设计分布式哈希表

核心挑战:k-mer 分布在多个 rank 上,如何高效存取?

2

避免"平凡解"

关键约束:不能通过 RPC 调用本地 std::unordered_map。这看似简单,但违背了 PGAS 的设计意图——每个请求都要触发远程调用,性能极差。

正确做法:在 UPC++ 的框架下直接操作分布式内存,利用 rget/rput 的异步性和批量能力。

3

实现 contig 生成

哈希表构建完成后,需要遍历 k-mer 图生成 contigs:

4

扩展性实验

设计两组实验:

绘制扩展图(speedup vs rank 数),分析扩展效率。


常见陷阱

平凡解陷阱: 用 UPC++ 的 RPC 调用本地 std::unordered_map 是最容易走的路,但性能极差且违反题目要求。评委会检查你的实现方式。

同步过度: 每次 rget/rput 都等待完成会丧失异步优势。应该批量发起操作,最后统一 wait。

数据倾斜: 基因组中某些 k-mer 出现频率极高(如重复序列)。简单的哈希分区可能导致某些 rank 负载过重。

忘记 barrier: UPC++ 的异步操作需要显式同步。在关键阶段后忘记 upcxx::barrier() 会导致数据竞争。

内存泄漏: upcxx::new_ 分配的内存必须用 upcxx::delete_ 释放。分布式内存泄漏比本地更隐蔽且更致命。


UPC++ 编程模式

One-Sided 通信

// rank 0 读取 rank 1 上的数据
upcxx::global_ptr<int> remote = ptr;  // rank 1 上的指针
int value;
upcxx::future<> fut = upcxx::rget(remote, &value, sizeof(int));
// ... 做其他工作 ...
fut.wait();  // 等待读取完成

批量操作

// 批量 rput:聚合多次写入为一次通信
std::vector<upcxx::global_ptr<int>> dst_ptrs;
std::vector<int> src_values;
// ... 填充数据 ...
upcxx::future<> fut = upcxx::rput(
  src_values.data(), dst_ptrs.data(), sizeof(int) * dst_ptrs.size()
);
fut.wait();

分布式对象

// 在所有 rank 上分配一个对象
upcxx::global_ptr<int> ptr = upcxx::new_<int>(0);

// 检查 affinity(该指针指向哪个 rank)
int owner = ptr.where();

// 仅在拥有者 rank 上操作
if (owner == upcxx::rank_me()) {
  *ptr.local() = 42;  // 转为本地指针访问
}

性能分析方法

报告要求

关键指标


与课程的关系

Lecture 11 · UPC++ PGAS
↓
one-sided 通信 → 分布式哈希表
Lecture 7-8 · MPI
↓
分布式内存模型 → 对比理解 PGAS
Lecture 9-10 · GPU
↓
并行思维 → 分布式图遍历
Lecture 24 · Comp Biology
↓
生物信息学应用 → 基因组组装背景

HW3 是 PGAS 模型的实战演练。UPC++ 的思想在后续课程中继续延伸:


学习心态

PGAS 是一种新的思维范式。 从 MPI 的"发消息"到 UPC++ 的"直接读内存",你需要重新思考数据放置和访问模式。Affinity 是关键——数据在谁的手里,谁访问它最便宜。

基因组组装是一个不规则应用(irregular application):k-mer 分布不均、图结构不规则。这正是 PGAS 擅长的场景——one-sided 通信天然适合不规则访问。

卡住时的正确顺序: 复习 Lecture 11 → 读 UPC++ 文档 → 从单机单 rank 开始调试 → 逐步增加 rank 数 → 分析通信瓶颈。


自测:你准备好了吗?

问题 1:UPC++ 的 global_ptr<T> 包含哪些信息?

问题 2:为什么 UPC++ 的 rget/rput 比 MPI 的 MPI_Send/MPI_Recv 更适合不规则通信?

问题 3:为什么 HW3 禁止通过 RPC 调用本地 std::unordered_map?

问题 4:在 UPC++ 中,为什么批量操作(batching)对性能至关重要?

问题 5:在基因组组装中,"k-mer"是什么?


导读要点

目标

UPC++ 并行化基因组组装

核心

分布式哈希表 + one-sided 通信

约束

禁止 RPC 平凡解

产出

扩展图 + 优化讨论 + 实现描述