2. 跨模块通信基础:模块划分、通信接口与数据流归纳总结
跨模块通信这件事,说简单也简单,说复杂也复杂。我见过太多项目,前期觉得模块划分无所谓,等到了性能优化阶段,才发现通信延迟已经把帧率拖垮了。这一章我们先打好地基,把模块划分、接口设计和数据流梳理清楚。这三样东西理顺了,后面排查延迟问题才能事半功倍。
2.1 模块划分——先画好边界
模块划分的核心就四个字:高内聚、低耦合。但落实到实际项目中,怎么分才合理?我个人习惯从两个维度来思考。
第一个维度:按功能域划分。说白了,就是把干同一类事的代码放到一起。渲染管线放一个模块,物理计算放一个模块,资源管理放一个模块。这样每个模块职责清晰,内部的改动不会影响外部。
第二个维度:按数据流划分。有些场景下,功能域划分不够用。我记得有一次做一款实时渲染工具,发现渲染模块和逻辑模块之间通信极其频繁,数据来回倒。后来我们把这两个模块合并成了一个更大的渲染管线模块,通信延迟直接降了40%。
下面是我总结的模块划分的几个关键原则:
- 粒度适中——模块太细,通信开销会让你崩溃;模块太粗,耦合度会让你改一处崩全局。
- 边界清晰——每个模块对外暴露的接口尽量少,接口越少,通信路径越可控。
- 依赖单向——尽量避免循环依赖。循环依赖是延迟排查的噩梦,我碰到过好几次。
2.2 通信接口——定义好“语言”
模块划分清楚了,接下来就是模块之间怎么说话。接口设计得好不好,直接影响通信延迟。
通信接口从大的方向分,就三种:
| 接口类型 | 典型场景 | 延迟特征 |
|---|---|---|
| 事件/消息队列 | 异步任务、输入处理 | 低延迟,但有排队积压风险 |
| 共享内存 | 高频状态同步 | 极低延迟,但需处理读写冲突 |
| RPC/函数调用 | 跨进程或跨线程调用 | 延迟较高,阻塞风险大 |
这里我想重点说一个坑:同步阻塞调用。我在项目中遇到过这样一个案例——逻辑模块调用渲染模块的接口,用的是同步RPC,渲染模块处理一帧需要16ms,逻辑模块就在那干等。结果就是逻辑模块的帧率被硬生生拖到了和渲染模块一样。你想想看,这其实完全可以用异步队列来解决。
2.3 数据流归纳——画出你的“物流图”
模块和接口都搞定了,接下来要回答一个问题:数据到底是怎么走的?从输入到输出,中间经过哪些模块?每个环节花多少时间?
数据流归纳,说白了就是给系统画一张“物流图”。我建议你按这几个步骤来:
- 列出所有数据源和数据目的地——比如输入设备、网络包、渲染输出等。
- 画出数据经过的每个模块——从源头到终点,一个模块一个模块地画。
- 标注每个接口的通信方式——是同步还是异步?是共享内存还是消息队列?
- 测量每段路径的耗时——用日志或者profiling工具打点。
做完这一步,你就能看到数据流中的“拥堵路段”在哪。举个例子,我几年前优化一个AR应用,画完数据流图发现:视频帧数据在送到渲染模块之前,在资源模块里做了一次多余的格式转换。那一次转换占用了3ms,去掉之后帧率直接从50fps升到了60fps。
2.4 模块、接口、数据流的协同关系
这三者不是孤立的。模块划分决定了接口的数量和复杂度,接口设计决定了数据流的效率,数据流分析又能反过来验证模块划分是否合理。它们形成了一个闭环。
下面这张图总结了本章的核心逻辑:模块划分是骨架,通信接口是连接,数据流是血液。延迟问题往往就出在接口堵塞或者数据流绕路上。
你在这张图里能看到三个核心信息:
- 模块划分(蓝、绿、橙三个模块)——各自独立,职责单一。
- 通信接口(紫色事件队列、青色共享内存)——连接模块的“桥梁”。
- 数据流路径(红色箭头)——从逻辑到渲染到资源,形成完整链路。
延迟爆发点往往出现在接口处或者数据流的关键转折点。比如图中的 B→C 段,共享内存如果没用好,数据拷贝和同步等待就会在这里堆积。
本章小结
模块划分、通信接口和数据流归纳,这三件事是跨模块通信的地基。地基没打好,后面做延迟排查就像在迷宫里找路——累还不一定找得对。
- 模块划分要做到高内聚低耦合,粒度适中,依赖单向。
- 通信接口要根据场景选择合适的类型,同步调用一定要谨慎。
- 数据流归纳能帮你直观看到延迟出现在哪里,是排查问题的第一步。