做体系结构这行大家工位上都离不开两样东西设计规则和性能公式。规则负责划底线告诉你哪些方向不能碰公式负责算账让你在动手之前就能把性能、功耗、面积这些数字估个大概。很多刚入行的朋友问我体系结构怎么上手我的答案一直很简单先把这批规则和公式用熟思路自然会打开。这篇文章就把我实际工作中最常用的一批“规则与公式”整理出来覆盖CPU性能方程、Amdahl定律、功耗模型、存储层次和并行设计讲清楚每个公式背后的直觉、适用边界和踩过的坑。适合正在做处理器设计、服务器选型、应用性能优化或者系统架构的读者刚入门的人也能跟着一步步算下来。1. 性能公式三件套先把“快”这件事量化体系结构里一切讨论的前提是能量化“快”。光说“这个设计更快”是没用的你得能回答快多少、为什么快、瓶颈在哪。这也是为什么性能公式是整套体系结构的地基。这一章我把最基础也最常用的三件事梳理一遍CPU性能方程、CPI分解、性能对比的方法。1.1 CPU性能方程一条指令的旅行成本CPU性能方程的写法有很多种核心就一个CPU时间 指令数IC× 平均每指令周期数CPI× 时钟周期时间CT也可以写成CPU时间 指令数 × CPI / 时钟频率这三个变量分别代表程序执行路径上的三笔账。指令数是程序动态执行的总指令条数不是源代码行数也不是静态指令条数而是每次循环迭代、每个函数调用都算进去的动态值。平均CPI是每条指令平均消耗的时钟周期数它跟负载类型高度相关整数运算、浮点运算、访存密集型的CPI差别很大。时钟周期时间是硬件主频的倒数代表数字电路最基本的节拍。实际用这条公式的时候我习惯先把它当作一个检查清单。拿到任何一个性能问题先问自己是IC太高了是CPI太高了还是时钟频率上不去只要定位到某一个变量优化方向就清晰了。比如某次处理一个benchmark异常变慢的问题单看热点函数发现指令数没有多大变化但CPI从2.1涨到了3.8最后排查下来是数据对齐导致的缓存访问冲突这就是典型的CPI问题。如果一开始就盲目改算法试图降指令数方向就错了。1.2 CPI分解从整体到局部的拆解CPU性能方程里的CPI是一个平均值但平均值掩盖了很多信息。做体系结构的人需要把CPI拆开拆到指令类别级别才能知道瓶颈的真正来源。CPI Σ指令类型i的比例 × 指令类型i的CPI_i比如一个负载里有30%的访存指令其中L1 Cache命中率是95%L1命中需要4个周期L2命中需要12个周期内存需要200个周期那访存这部分贡献的CPI就能大致算出来。很多时候算完才发现明明整体CPI只有2.5但访存部分就贡献了1.8这时候去优化ALU执行单元的延迟毫无意义应该把精力放在Cache预取、加载队列或者内存调度上。这里有一条很实用的规则先拆CPI再定优化点。任何想做的微架构改动先试着把它的量化影响折算到CPI方程里得到一个预期收益。如果预期收益小于5%那这个改动在大多数场景下都不值得做——因为引入的复杂度和风险往往大于它带来的收益。1.3 性能对比的正确姿势体系结构工作里最常出错的地方不是公式不会算而是对比的时候没有控制变量。两个处理器比性能如果频率一个高一个低不能直接说高的就快。一定要回到CPU时间公式把IC、CPI、CT三个变量分开比较。某平台上指令数多20%但CPI低30%频率低10%总时间可能反而更短这种情况很常见尤其在不同指令集架构之间对比时。还有一个容易踩的坑是平均值的选择。对比多个benchmark的加速比时我见过好多人直接用算术平均这其实是错的。加速比是比例数据两个benchmark各加速2倍和0.5倍算术平均是1.25倍看起来还不错但几何平均是1倍也就是说整体根本没有净收益。标准做法是用几何平均来汇总加速比。这条我记得很清楚因为自己早年间在一个对比评估里就因为这个算错了结论后来被同事指出来从那以后凡是遇到比例型的性能数据我第一反应就是几何平均。2. Amdahl定律优化收益的天花板如果说CPU性能方程是“算账”的基础Amdahl定律就是“预期管理”的基础。它讲的是一个很朴素的道理系统里不可并行、不可优化淘汰的部分最终会限制你所有努力的上限。这条定律做体系结构的人绝对躲不开做并行程序优化的人更躲不开。2.1 公式的直觉与推导Amdahl定律可以写成一个很简单的式子加速比 S 1 /(1 - P) P/NP是程序中可并行的比例N是并行度1-P是串行部分。当N趋向无穷大的时候加速比趋于1/(1-P)也就是说即使你有无限多核一个含10%串行部分的程序加速比上限也就是10倍。我常用一个生活类比来理解它流水线包装流水线。假设一份活计里10%的工作必须由一个人单独完成剩下90%可以分给100个人同时干。100个人一起干的总时间是单独花时间加上并行部分花时间远小于100倍。串行部分是那根最慢的线不管后面有多少人排队总进度都被它卡住。这个公式的价值在于它给了你一个事前判断工具。想提高并行系统的性能先不要急着加核先算出当前P是多少再根据极限加速比决定值不值得投入更多并行资源。如果P0.9极限是10倍你已经达到9倍了这时候再加核收益极其有限应该把精力转去改造串行部分。2.2 多核时代的Amdahl与效率曲线在多核处理器设计里Amdahl定律直接影响核数和单核性能的权衡决策。如果只看并行加速比核数越多越好但现实是核数增加后单个应用能利用的并行度有限同时功耗面积都涨。设计团队在定核数时会做一个典型的计算给定一个目标负载集合统计每个负载的P值然后画出核数-加速比曲线在曲线上方标出功耗预算线两条线的交点往往就是核数取舍点。另一个相关公式是效率效率 E S_p / pp个核得到的实际加速比是S_p如果E可以测试E1说明有资源在空转。做并行优化时我一般先看E。如果16个核的机器上S_p10E0.625说明有6个核的算力都花在同步和通信上优化同步比加核划算得多。需要提醒的一点Amdahl定律有个隐藏假设问题规模是固定的。如果问题规模随核数增长而增长那就到了Gustafson定律的语境里结论会乐观不少。实际工程里两种场景都有强扩展模型处理固定数据集弱扩展模型处理按比例增大的数据集用之前先搞清楚当前场景属于哪一种否则公式会误导你。2.3 误用Amdahl的典型姿势我把Amdahl定律的误用总结成三种。第一种是把P当成了理论并行度而不是负载实测并行度。有些人写并行程序时想当然地认为矩阵乘法是100%可并行的于是按P0.99去估算加速比结果实测只有0.85因为内存带宽、缓存共享、同步点这些开销根本没算进去。P一定要来自profile不能拍脑袋。第二种是忽略了通信和同步的开销。Amdahl的经典形式只算了“串行并行”两部分但真实系统里并行部分之间还有数据交换成本。这个成本不随核数扩展而消失反而可能因为竞争加剧而上升。严谨一点的写法会把通信开销单独建模工程上我常常把Amdahl算出来的理论上限再打一个七折八折作为预期。第三种是把公式结果当作绝对预测。Amdahl是简化模型适合做方向判断不适合做精确性能预报。真正动手前还是要用模拟器或者实测来验证。我的习惯是先用Amdahl做粗筛把不值得并行化的方案过滤掉再把剩下的方案逐个实测。3. 功耗与能效暗硅时代的硬约束性能公式管“跑得快不快”功耗公式管“跑得起跑不起”。最近这些年的处理器设计功耗已经取代性能成为第一约束。之前有一段时间大家都强调拼频率拼核数后来发现功耗墙一堵什么宏大的规划都绕不过去所以功耗相关的规则和公式必须单独拿出来讲。3.1 动态功耗公式芯片发热的根源数字电路动态功耗的基本公式P_dynamic α · C · V² · fα是活动因子就是一个时钟周期内平均有多少晶体管在翻转C是负载电容跟工艺、门数和走线有关V是供电电压f是时钟频率。每一项都有明确的物理含义但这公式最核心的洞察藏在V²里——电压在公式里是平方项意味着电压是所有因素里最敏感的杠杆。我们来实际算一笔账。假设某芯片当前电压是1.0V频率是2.5GHz我们把电压降到0.9V频率因为电压下降不得不降到2.25GHz。性能下降10%动态功耗变成0.9² × 0.9约等于0.73也就是省了27%的功耗。拿10%的性能换27%的功耗很多场景下是值得的这就是动态电压频率调节DVFS不但在移动芯片上普及在服务器上也被大量使用的原因。工程上我用这个公式的时候会特别注意α。有些设计的功耗估算为什么会比实测低很多多半是α取小了。芯片里不是所有模块每个周期都在翻转但总线、时钟树、存储阵列的活动率往往被低估。不同模块的α差别很大要按模块分别估算不能一个数走天下。3.2 暗硅规则核数不是越多越好的原因暗硅规则是这些年来理解多核处理器功耗约束的一句口头禅。它说得很直白因为功耗墙的存在一个芯片里可以有几十亿个晶体管但在任意时刻能同时工作的晶体管只占其中一部分剩下的部分必须断电或降频否则芯片功耗超出封装散热能力。这里有个简单的估算方法。假设某个单核满载功耗是30W芯片的功耗预算TDP是100W那么大致可以算出最多同时运行约3个满负载核。如果你设计的是8核芯片那就意味着除非工作负载远远达不到单核峰值功率否则总会有核在做功耗管理。这个估算虽然粗糙但第一眼就能看穿很多过度设计的方案。暗硅规则直接推动了异构计算的发展。既然不能所有核都满载跑不如把一部分面积做成高效小核在能效比上做文章。某芯片上的大核跑单线程性能小核跑并行吞吐总功耗控制在预算内实际上就是在这条规则下玩设计权衡。3.3 能效指标选型与设计都要算的账能效指标的通用形式是每瓦特性能能效 有效运算量 / 总功耗常见的有每瓦特多少GFL OPS、每瓦特多少帧、每瓦特多少会话。做服务器选型和加速卡选型时除了看峰值性能我都会把能效也列出来。数据中心成本里电费占比并不小峰值性能再高能效太差白天也不敢全速跑而能效账算清楚后很多看似性能差距不大的方案高下立见。体系结构设计里能效目标会影响从微架构到制程的所有决策。比如流水线深度增加能提升频率但每提升一档频率动态功耗和漏电功耗都随之上升超过某个点之后每瓦性能反而下降。前几年的“频率竞赛”就是在功耗墙上撞了南墙所以现在主流处理器开始转向更宽的核、更低的主频和更多的核外并集。顺带说一句漏电功耗。工艺越先进静态漏电P_leak V × I_leak占的比重越大先进制程下漏电和动态功耗经常是一个量级。所以现代芯片里大量出现电源门控、睡眠态、睿频管理本质都是为了控制漏电。4. 存储层次规则跑得快不如cache得住存储层次是体系结构里最“规则密集”的领域。CPU性能方程里访存部分经常是最大的CPI贡献者功耗公式里存储阵列是活动率和漏电大户并行设计里数据搬移往往比计算更贵。所以存储层次相关的规则值得单独占一章。4.1 局部性原理缓存存在的前提缓存一切设计的前提是局部性原理拆成两条时间局部性——刚被访问的数据大概率很快还会被访问空间局部性——刚被访问数据附近的数据短时间内也大概率会被访问。循环体里的变量是时间局部性顺序遍历数组是空间局部性。没有局部性原理缓存就是一个纯浪费面积的设计。所以做性能优化时我会把这句规则当成“代码级优化”的指导。通过调整循环顺序让内层循环尽量访问连续的地址把热点结构体按访问频率重新排列字段让频繁访问的成员待在同一个缓存块里——这些都是在制造局部性实际上就是在制造缓存命中率。举个例子某次优化一个图像处理模块原本内层循环先遍历行再遍历列但数据存储是列优先的缓存命中率只有六成多。把循环顺序对调之后命中率直接到了九成以上性能提升了将近一倍。这个收益不需要改任何算法只是让访问模式贴合了缓存的组织方式。4.2 平均访存时间公式三个杠杆的权衡衡量存储层次好坏的核心公式是平均访存时间AMAT 命中时间 缺失率 × 缺失代价命中时间是命中时拿数据要花的周期缺失率是每多少次访问会漏到下一级缺失代价是漏一次要额外花的周期。L1 Cache命中时间通常几个周期主存缺失代价几百个周期所以缺失率哪怕只降一点点总效果都非常显著。缓存设计有三个主要杠杆容量、块大小、相联度。容量增大缺失率下降但命中时间和面积功耗同步上升块大小增大空间局部性利用更好块大小过大Cache里有效数据占比下降缺失率反而反弹相联度从直接映射到两路、四路、全相联缺失率逐步下降但命中时间的硬件电路复杂度也在上升。设计的时候就是在这三个参数之间找一个组合。我自己的设计经验是优先确定缺失代价最大的那一级然后大胆地在这一级上用高相联度L1尽量保持小而快牺牲一点命中率换取低命中时间L2/L3容量和相联度可以放开一些。另外块大小要结合目标负载来分析流式处理多的负载适合大块随机访问多的负载适合小块。4.3 存储墙与队列深度的估算存储墙说的是处理器性能和内存性能增长速度之间的鸿沟。处理器性能每年增长约50%内存带宽提升也有但延迟下降速度远跟不上结果是内存延迟在整个系统延迟里的占比越来越大。体系结构里对抗存储墙的基本思路是分层、预取、乱序执行、多线程隐藏延迟。这里有个小工具叫Little定律学排队论的时候会碰到做存储系统设计时非常有用L λ × WL是系统中的平均请求数λ是平均到达率W是每个请求的平均等待时间。它告诉我某内存请求到达率是每秒1000万次平均等待时间是200纳秒那系统里同时存在的请求数大约是2个。反过来说如果你知道延迟是200纳秒希望达到的吞吐率是每秒1000万次你就需要至少2个并发请求槽位——这个数直接对应Cache缺失处理寄存器MSHR的设计深度或者内存控制器的队列深度。这条公式看起来简单但很多团队在调队列深度时还是在凭感觉。实测之后用Little定律一算发现队列深了一倍但吞吐没变化再深查才发现延迟早已被带宽限制住了队列再深也是白搭。5. 流水线与并行设计规则多条路才能有高吞吐性能公式和存储规则解决的是“单条路”上的优化流水线和并行设计则是“多条路”的学问。处理器内部的流水线越深并行度越高芯片上的核越多系统吞吐越依靠并行。这一章把流水线和多核并行里几个最基础的公式和设计规则过一遍。5.1 流水线加速比理想和现实的差距流水线的核心公式说理想情况下一条k级流水线能把吞吐率提高到k倍理想加速比 k前提是每条指令需要k个周期才能完成但每隔1个周期就能完成一条新指令。但现实里流水线做不到理想加速比因为有三种停顿结构冲突硬件资源不够用、数据冲突指令之间有数据依赖、控制冲突分支跳转让后面预取的指令作废。这些停顿一出现加速比立刻掉下来。设计流水线时有一个经验曲线流水级数从5级增加到10级性能提升明显从15级增加到20级提升就很有限了因为每一级之间的寄存器延迟、时钟偏斜和功耗都在增加。某团队早年做过一个实验把流水线从12级加深到20级频率提升了约25%但单条指令的周期数也增加了整段性能提升不到10%功耗却涨了30%以上。从那以后我们定了一条内部规则提频率之前先算流水线收益函数收益小于代价就放弃。5.2 并行效率公式与可扩展性分析多核并行设计里核数、加速比、效率三者之间的关系是效率 E S_p / p这个公式就是个试金石。效率接近1说明加核带来的加速接近线性效率掉到0.5以下说明有一半核在等同步、等数据、等锁。我拿到任何一个并行程序的profile第一眼就是看效率随核数变化的曲线如果效率随核数急剧下滑再增加核数也没有意义。并行程序优化的经验规则很多我印象最深的是一条关于任务粒度的任务粒度要远大于同步开销。如果同步一次要20微秒单个任务算40微秒那一半时间都在同步效率必然很低。把任务粒度调到同步开销的5到10倍以上效率才可能上去。这条规则配合并行效率公式基本能预判一次并行化改造成败。另外要强调一点并行程序的瓶颈往往不在计算在数据移动。通信/计算比这个词天天挂在嘴边实际做的时候我会先算每一步需要移动多少数据需要多少带宽然后把带宽约束带进效率公式里。很多宣称“并行效率0.9”的程序实际跑起来一查每个线程都在等内存算力根本没有喂饱。5.3 带宽、延迟与吞吐率三种视角的换算系统设计经常需要换算三类指标延迟、带宽、吞吐率。延迟是单个请求从发出到完成的时间带宽是单位时间能搬移的数据量吞吐率是单位时间完成的任务数。它们之间不是简单倒数关系因为系统里有并发。吞吐率 并发数 / 延迟这个公式其实跟Little定律等价。要提升吞吐率要么增大并发数要么降低单请求延迟。一个系统如果延迟高但并发能力很强吞吐率也能做得很大这就是为什么深流水线和多线程能在高延迟的内存系统上仍然保持较高吞吐。工程上做设计取舍时我会把这几条公式画成一个简单的量级表L1命中4周期、L2命中十几周期、主存命中几百周期、访问磁盘几百万周期。每做一次跨层数据搬移心中大概有数。做矩阵运算的加速卡设计时如果片外带宽是1TB/s一次要读4字节数据算2次浮点那理想吞吐率上限就是每秒250万亿次浮点。超过这个数瓶颈不在计算流水线而在数据搬运。6. 常见问题与避坑经验公式误用现场就算公式列得再清楚实际操作中还是会踩坑。这一章我把自己和身边同事踩过、见过的高频坑整理成一个速查表每条都对应前面的某个公式或规则方便你直接把坑绕过。6.1 使用公式前先问“假设是什么”所有公式都有适用条件。CPU时间方程假设三变量可以独立评估但实际优化IC会影响CPI优化CPI可能影响频率三者之间耦合紧密。Amdahl定律假设并行部分无通信开销而真实系统里通信开销随核数增长。功耗公式里的α很难从架构图直接读出往往依赖仿真与实测标定。我处理这类问题的方法是用公式算出来的数一律当成“量级参考”而不是设计要求。比如Amdahl说理论上限是20倍我设定一个实测目标12倍功耗公式估算芯片功耗30W我只把它当基线详细设计时要逐模块用工具重新验证。公式的价值在于把方向摆对而方向的正确比精度重要得多。6.2 几个容易出错的细节最容易出错的三个细节我一个个看过身边人踩过。第一单位换算。时钟周期和频率之间的倒数关系有人拿纳秒和GHz直接相除得出一个离谱的周期数。2GHz时钟下周期是0.5纳秒如果缺失代价是200周期那就是100纳秒不是直接用200纳秒。第二比例数据的平均值。我前面已经提到性能加速比这类比例数据要用几何平均千万别用算术平均。算术平均会被个别极端数据带偏这在多benchmark汇总时非常常见。第三峰值性能和实际性能的混淆。某些芯片标称峰值吞吐率需要同时满足一堆理想条件实际负载根本达不到。做架构评估时一定要用代表负载的实测数据不能拿峰值参数直接套公式。6.3 常见问题速查表现象可疑原因对应公式/规则避坑要点加了核性能没涨多少串行瓶颈Amdahl定律先算P再决定加核优化了指令数总时间反而变长CPI恶化CPU时间方程三个变量一起看功耗比估算高很多活动因子取小了PαCV²f分模块标定α缓存替换算法换了效果不大缺失代价太大AMAT先压缺失代价并行效率很低任务粒度太小效率S_p/p粒度至少是同步开销5倍队列加大吞吐不动带宽瓶颈Little定律先查带宽再谈队列这些坑说到底一句话公式和规则是工具不是银弹。用之前要理解假设用之后要拿实测校准。我个人在实际操作里有个习惯每次做一个架构改动之前把相关公式列出来算一算预期的性能、功耗和面积变化写成一个“预期卡”然后等实测数据出来以后把预期和实测一对。对上了说明改动符合预期对不上通常不是实测错了是我对系统的理解有漏洞。这个习惯让我避免了不少自嗨型优化。最后再分享一个实用小技巧当你不确定某个公式该怎么用的时候找一个最小的数值例子手算一遍感受公式里每一项的量级再回到真实场景中。体系结构里的公式大多不复杂真正重要的是理解每一项背后的物理含义和工程代价。