Skip to content
团子云技术 Lite 1.048596
Go back

Mooncake TE 阅读手记-13-高性能编程线程模型

团团虾导读:从 Mooncake 源码中提炼出的高性能编程模式总结。逐一分析了 RTC、Pipeline、Thread-per-Core、Submit-then-Poll、Event-Driven、Zero-Copy DMA 和 Adaptive Polling 七种模型,每种都给出来源文件、代码片段和 Mooncake 中的具体体现。最后一张对比表总结了它们在不同维度上的取舍。

文章三:高性能编程线程模型 — 不止 submit-then-poll

从数据传输和网络框架的角度分类

1. Run-to-Completion (RTC)

一个线程拿到请求后从头处理到尾,不切换上下文。

epoll_wait → accept → read → 业务逻辑 → write → epoll_wait

适用场景:业务逻辑轻量、每请求处理时间短(微秒级)。典型代表:Seastar、ScyllaDB、DPDK 应用。

Mooncake 中的影子:TcpTransport 服务器端的 ServerSession(tcp_transport.cpp:63-243),每个 socket 连接上的读-写流程由一个 shared_ptr<ServerSession> 回调链驱动,逻辑相对完整闭环。但这不算严格 RTC,因为它在 asio 事件循环中分步执行。

2. Pipeline(流水线)

将处理流程拆成多个 stage,每个 stage 由独立的线程或线程池处理,stage 之间通过队列通信。

[Stage 1: 解析] → Queue → [Stage 2: 验证] → Queue → [Stage 3: 写入]

适用场景:处理流程复杂、各阶段耗时差异大、需要利用多核并行。典型代表:GPU 推理的 prefill→decode pipeline、流式处理框架(Kafka Streams)。

Mooncake 中的影子:MultiTransport 将请求按 Transport 分组后分别提交,每个 Transport 的 WorkerPool 再进一步按 NIC(RdmaContext)分发。形成两级流水:请求→按协议分流→按NIC分流→QP处理

3. Thread-per-Core

每个 CPU core 固定绑定一个线程,所有数据按 core 分片(sharding),避免跨核通信和锁竞争。

Core 0: [Worker-0] → 处理 shard 0 的数据(slice_queue[0, 2, 4, 6])
Core 1: [Worker-1] → 处理 shard 1 的数据(slice_queue[1, 3, 5, 7])

适用场景:高吞吐、低延迟、NUMA 感知。典型代表:DPDK、memcached。Seastar 也是 Thread-per-Core + RTC。

Mooncake 中的体现:WorkerPool 的 shard queue 设计(worker_pool.h:61-64):

const static int kShardCount = 8;
std::unordered_map<std::string, SliceList> slice_queue_[kShardCount];
std::atomic<uint64_t> slice_queue_count_[kShardCount];
TicketLock slice_queue_lock_[kShardCount];

8 个 shard 各有一把 TicketLock。Worker 线程按 thread_id 台阶式轮询 shard(shard_id = thread_id; shard_id < kShardCount; shard_id += kTransferWorkerCount),避免热点锁竞争。

4. Submit-then-Poll(Mooncake 的主要模式)

应用线程提交工作后主动轮询完成状态。这是 Polling-based 异步模型:

// 提交
submitTransfer(batch_id, requests);

// 轮询
while (true) {
    for (int i = 0; i < batch_size; i++) {
        getTransferStatus(batch_id, i, status);
        if (completed) break;
    }
    if (all_done) break;
}

优点

缺点

Mooncake 中的使用transfer_engine_bench.cpp:418-431 展示了标准的 submit-then-poll 模式。

5. Event-Driven(事件驱动)

工作完成后通过回调或条件变量通知等待者,提交线程可以休眠或做其他事。

submitTransfer(batch_id, requests, callback);

// 或者
submitTransfer(batch_id, requests);
// 等待条件变量通知
condition_variable.wait(lock, [&]{ return batch_done; });

Mooncake 中的 Event-Driven 模式(当 USE_EVENT_DRIVEN_COMPLETION 宏开启时):

mooncake-transfer-engine/include/transport/transport.h:334-342BatchDesc 包含了事件驱动的相关字段:

struct BatchDesc {
    // ...
    std::atomic<uint64_t> finished_task_count{0};
    std::mutex completion_mutex;
    std::condition_variable completion_cv;
};

Slice::check_batch_completion()transport.h:187-239)在最后一个 task 完成时唤醒等待线程:

if (prev + 1 == batch_desc.batch_size) {
    std::lock_guard<std::mutex> lock(batch_desc.completion_mutex);
    batch_desc.is_finished.store(true, std::memory_order_release);
    batch_desc.completion_cv.notify_all();
}

6. 零拷贝 + DMA(数据流优化模式)

CPU 完全不参与数据搬运,数据通过 DMA 在设备间直接传输。

GPU VRAM → [GPU DMA] → PCIe → [RNIC DMA] → RDMA 网络 → [RNIC DMA] → PCIe → 远端 GPU VRAM

Mooncake 中的体现:RDMA Transport 使用 ibv_post_send 提交 Work Request 到 QP,网卡自动执行 DMA 读写。对于 GPU 内存,RDMA Transport 通过 ibv_reg_dmabuf_mr()(DMA-BUF)或 ibv_reg_mr()(nvidia-peermem)注册 GPU 内存(rdma_context.cpp:238-332),数据传输走 DMA 路径。NVLink Transport 使用 allocateFabricMemory 实现跨节点 GPU 内存直接访问。注意:RDMA 路径使用的是 DMA 而非 cudaMemcpy,后者用于不同的传输路径。

7. Adaptive Polling(自适应轮询)

在短等待时使用 busy-poll 以获得低延迟,在长等待时退回休眠/条件变量等待,两者的平衡自适应调节。

Mooncake 中的实现:WorkerPool::transferWorker 的等待逻辑(worker_pool.cpp:393-414),以下为展示核心思路的伪代码:

// 伪代码:展示 adaptive polling 核心逻辑
// 实际代码包含 mutex + double-check 模式避免 lost wakeup

// 当所有 slice 都处理完毕时
if (processed_slice_count == submitted_slice_count) {
    uint64_t curr_wait_ts = getCurrentTimeInNano();
    if (curr_wait_ts - last_wait_ts > kWaitPeriodInNano) {  // 100ms
        // 超过 100ms 没有新工作,进入休眠
        suspended_flag_.fetch_add(1);
        cond_var_.wait_for(lock, std::chrono::seconds(1));
        suspended_flag_.fetch_sub(1);
    }
    continue;  // 如果不到 100ms,继续 busy-poll
}

先 busy-poll 100ms,如果仍无新工作才进入条件变量等待。这避免了频繁的线程唤醒/休眠切换开销。实际代码中使用了 std::unique_lock<std::mutex> 加锁和 double-check 模式(获取锁后重新读取原子计数),以避免经典的 lost wakeup 问题。

对比总结

模型延迟吞吐CPU 利用率适合场景
RTC极低高效(无切换)简单、快速业务
Pipeline中等高(多核)复杂流式处理
Thread-per-Core极高高效(无锁)多核高并发
Submit-then-Poll低(CPU空转)数据传输、I/O密集
Event-Driven中等中-高请求响应、回调式
Zero-Copy DMA-极高带宽极低(CPU不参与)大数据块传输
Adaptive Polling低-中负载波动大的场景

在 Mooncake Transfer Engine 中,实际上组合使用了多种模型:整体 Submit-then-Poll 作为上层接口,内部 RDMA Transport 使用 Thread-per-Core 式的 shard 工作池 + Adaptive Polling,数据传输路径为 Zero-Copy DMA,而 USE_EVENT_DRIVEN_COMPLETION 宏开启了 Event-Driven 的选项。



Share this post on:

Previous Post
Mooncake TE 阅读手记-14-RDMA 内存注册与 lkey/rkey
Next Post
Mooncake TE 阅读手记-12-Transport 核心概念与线程模型