CPU时间公式与Amdahl定律:体系结构性能分析的量化基石
前阵子帮某项目组做性能选型两个方案连吵了三天一个押高频一个押低功耗。我把几行公式搬出来算了一轮结论让两边都愣住——高频方案并没有想象中快功耗账一摊开反而更难看。那一刻我真切感觉体系结构里最值钱的往往不是灵光一现而是那些被反复验证的规则与公式。这是体系结构系列里的一篇专门聊聊规则与公式它们怎么来的、怎么用、又藏了哪些坑。1. 从一道性能题说起CPU时间公式到底在算什么1.1 核心公式拆解为什么是三条边相乘所有性能讨论基本都绕不开这个式子CPU时间 指令数(IC) × 每条指令平均时钟周期(CPI) × 时钟周期时间(CT)其中时钟周期时间 CT 是主频的倒数。主频 2GHz一个周期就是 0.5ns。很多资料也把公式改写成CPU时间 指令数 × CPI / 主频道理完全一样只是写法更顺手。我第一次接触时觉得这公式太朴素后来才意识到它把所有性能优化手段都归置到了三个变量上减少指令数、降低每条指令的周期数、缩短周期时间。怎么理解三个量相乘你就想成盖章一摞文件是“指令数”每份文件需要盖多少个章是“CPI”盖一个章花多少秒是“时钟周期时间”。总工作量就是三个数乘起来。任何优化手段最终都要落到其中至少一个数上没有第四种出路。也正因为如此这个公式成了整篇内容的地基。后文所有定律和指标本质上都是在对这三个数再做细分或加权。1.2 三个变量互相拉扯每次改动都是一次权衡公式本身是静态的真实设计里三个变量却会互相咬合。你改其中一个另外两个往往跟着变而且方向不一定有利。提高主频通常要加深流水线或收紧时序。代价是分支预测失败、缓存未命中时浪费的周期更多CPI会上升。主频提升 20%CPI 从 1.5 升到 1.9总时间反而变成原来的 1.9/(1.5×1.2) ≈ 1.06 倍越优化越慢。降低 CPI比如上乱序执行、加大缓存芯片面积和功耗会明显上涨。对短任务来说启动和唤醒的固定开销可能直接把收益吃光。减少指令数则更多要指望编译器优化和指令集设计。但一条复杂指令虽然能替换掉几条简单指令这条指令在微架构里的 CPI 可能高得多算下来未必划算。这就是体系结构设计最反直觉的地方你以为在解一元方程实际上在解联立方程组。项目组里经常出现“优化了一项整体反而变慢”的情况多半就是只盯住单个变量忽略了公式里其他两个变量的连锁反应。2. Amdahl定律为什么加速是有上限的2.1 公式与生活直觉不可加速的部分是硬天花板Amdahl定律是体系结构里最著名的规则之一。假设程序中有比例 f 的部分能被优化优化后这部分加速为 s比如原来要 10 秒优化后要 1 秒则 s10那么整体加速比为加速比 1 / ((1 - f) f / s)式子里的 (1-f) 是没被优化的部分它始终按原速跑不管你优化得多猛这部分的耗时都会留在总时间里。生活里例子到处都是。你早上上班半小时坐高铁高铁速度提到多快都有意义但最后还有十分钟必须步行步行你怎么优化都只能走到那么快。把高铁优化成瞬移总路程时间也只是从 40 分钟变成 10 分钟不可能到 0。所以当 f0.9s 无限大整体加速比最多只有 1/(1-0.9)10 倍。90% 的部分无限加速总时间只缩短到原来的十分之一剩下那 10% 串行成了绝对天花板。这句话在并行计算、缓存优化、指令集扩展里通吃。2.2 真实项目里的“并行比例”应该怎么测理论公式不复杂真正坑人的是 f 怎么取值。很多开发者的习惯是打开代码数行数代码里 90% 的循环都能并行OKf0.9。这种估算几乎都会翻车。f 应该是“经过优化的那部分代码在基准测试总执行时间里实际占用的比例”而不是代码行数比例。同步等待、负载不均、串行初始化、I/O 空隙这些都不好从源码行数上看出来但 profiling 一测全暴露。我曾见过一个项目负责人拍着胸脯说算法 95% 并行实际开到 16 核只快了 5 倍。用 Amdahl 反推加速比 5极限条件下 f 至少要到 0.8也就是说这台机器上实际可并行比例只有八成左右剩下两成时间都耗在同步、任务分发和局部串行段里。外推到 64 核理论加速上限也只有 5 倍多继续加核基本没有回报。这个反推也很有工程价值如果你希望整体加速达到目标 S那么必须满足 (1-f) ≤ 1/S也就是说串行比例必须小到一定程度。想清楚这一点再决定要不要投钱做并行化能省下不少无用功。2.3 换个视角Gustafson法则与可扩展问题Amdahl定律看多了容易让人悲观。另一条规则我愿意一起拎出来讲Gustafson法则。Amdahl 假设问题规模固定所以并行部分计算到一定程度后串行部分占比会越来越大加速比封顶。Gustafson 换了一个假设随着处理器数量增加问题规模也可以扩大。大规模科学计算、图像渲染、数据分析都是这种场景。此时加速比可以写成加速比 (1 - f) N × f其中 N 是处理器数。同样 f0.95、N64Gustafson 给出约 61.85 倍远高于 Amdahl 的极限 20 倍。两条规则没有谁对谁错只是适用前提不同。搞工程时先问一句我的问题规模是钉死的还是可以跟着硬件长大问题钉死用 Amdahl 冷静问题可以放大Gustafson 会给你合理得多预期。3. MIPS、MFLOPS与功耗公式那些容易被误用的指标3.1 MIPS指标的三重陷阱MIPS 全称是每秒百万条指令计算式为MIPS 主频 / (CPI × 10^6)看起来比执行时间公式更直观主频越高、CPI 越低MIPS 越大。但用这个指标跨处理器比较几乎每一步都在踩坑。第一重坑不同指令集的指令“含金量”不同。某简单指令集处理器跑一个任务可能要用 200 条指令另一个复杂指令集处理器只需 50 条。前者 MIPS 可能是后者的三倍总执行时间却可能更长。举极端例子有些微指令能做“乘加”一条搞定有些指令集要先乘后加两条MIPS 高不代表程序跑得快。第二重坑CPI 是平均值而且依赖指令混合比例。同一颗处理器跑整数密集和浮点密集程序CPI 能差出好几倍。厂商宣传里常用的峰值 MIPS往往是选了一组最有利指令序列跑出来的真实应用根本到不了。第三重坑MIPS 没法衡量访存瓶颈。程序一旦卡在内存带宽上处理器再算得快也白搭而 MIPS 公式里完全没有内存。所以现在行业里正经对比性能基本都靠标准基准测试套装没人再拿裸 MIPS 说事。MIPS 保留下来更多是教学活动里的分析工具帮理解 CPI 与主频的关系。3.2 峰值算力与实际算力MFLOPS的另一个坑科学计算领域更常用 MFLOPS即每秒百万次浮点运算。它避开指令集差异因为在数值计算里“浮点运算次数”比“指令条数”更有可比性。但 MFLOPS 也有自己的问题峰值和实测之间隔着一条大鸿沟。硬件理论峰值大致是峰值FLOPS 处理器核数 × 主频 × 每周期浮点运算次数注意这里假设每个周期都打满浮点单元而且数据都在寄存器里排队等着算。真实程序还有访存、同步、分支、通信能达到峰值十分之一已经算很好的应用。做高性能计算的人经常挂在嘴边一句话峰值是宣传用的实测才是自己用的。想评估系统最好直接用代表目标负载的基准程序跑而不是拿厂商的峰值数字做外推。3.3 动态功耗公式里藏着最反直觉的节能逻辑处理器功耗往细了拆有很多项动态功耗、短路功耗、漏电功耗。第一项在常规分析里最受关注公式也很简单动态功耗 ≈ C × V² × fC 是负载电容V 是工作电压f 是时钟频率。很多功耗优化方案上来就盯着频率但看公式就知道电压是平方项杠杆大得多。同一颗片电压从 1.2V 降到 1.0V单独看功耗差约 30%频率从 2GHz 降到 1.8GHz只差 10%。所以现代处理器普遍在频率下降时连电压一起降这就是 DVFS动态电压频率调节的思路。更反直觉的点在能耗层面。能耗是功耗乘时间能耗 功耗 × 时间如果只降频率、不降电压功耗线性下降但跑同一段程序的时间几乎反比拉长两者一乘能耗基本没变。真正省能耗的是降电压频率降为原来的 α 倍电压降为原来的 β 倍执行时间拉长约 1/α动态功耗约变为 αβ² 倍总能耗约变为 β² 倍实打实往下掉。这就是为什么“降频不降压等于白忙活”。我也见过不少数据中心运维的误区一看负载低了就想着把节点主频降下来省电。结果只降频率没动电压任务时间拉长总能耗只是平移客户体验还变差。更合理的做法是评估后直接合并任务、关闭部分节点让剩下的节点在高能效点运行。这些判断靠的就是功耗公式加执行时间公式一起算。3.4 为什么这些“过时”指标还在被引用MIPS 和 MFLOPS 经常被嘲笑不靠谱但都没消失。原因之一是指标简单适合教学和快速估算原因是历史惯性老文档、老论文大量使用读的时候必须知道它水在哪。规则和公式本就不是用来替代实测的而是帮你建立数量级直觉。真正到做方案决策时回到执行时间公式、Amdahl 定律和功耗公式把账算到具体数值比嘴上争到底谁快有意义得多。4. 隐藏在规则背后的设计权衡几条值得记住的经验法则4.1 局部性原理性能优化的第一性规则如果说体系结构里只能记住一条经验法则我会推荐局部性原理。它由两部分组成时间局部性刚访问过的数据不久之后很可能再次访问。空间局部性访问了一个地址附近的数据相邻地址的数据也大概率会被访问。这条法则解释了缓存为什么有效也指导我们怎么写代码。经典的例子是遍历二维数组。大多数语言里数组按行存储// 按行优先遍历访问 arr[i][j] 时同一行的元素都在临近地址 for (int i 0; i N; i) { for (int j 0; j N; j) { sum arr[i][j]; } }如果内层循环改成按列遍历每次访问都要跨过一整行、跳到很远的内存地址缓存命中率会断崖式下降程序慢好几倍一点也不奇怪。另一个常见的 90/10 规则也属于这一类经验程序 90% 的执行时间通常花在 10% 的代码上。所以做性能剖析一定要先抓热点而不是均匀地优化每一行代码。4.2 存储层级间的“10倍法则”计算机体系结构里的存储层级可以看成一条延迟瀑布。寄存器最快往后是 L1、L2、L3 缓存、内存、硬盘。工程上常用数量级来建立直觉下表只是经验量级不同硬件差异不小层级访问延迟量级容量量级寄存器约 1 个时钟周期几十到几百字节L1 缓存约 2-4 个时钟周期几十 KBL2 缓存约 10-20 个时钟周期几百 KBL3 缓存约 30-60 个时钟周期几 MB 到几十 MB内存约 100-300 个时钟周期十几 GB 到几百 GBSSD约百微秒级几百 GB 到几 TB每往下一层延迟大约差一个数量级。这个经验法则的最大价值是提醒你缓存命中率永远是优化清单里最优先被检查的项目。很多算法看似复杂度低实际跑起来卡在内存访问上原因就是数据访问模式太差缓存帮不上忙。分析性能时我习惯先估算核心数据集能不能塞进 L2不行再看带宽而不是一头扎进微指令细节。4.3 核心微架构权衡乱序、深度流水线与大小核处理器设计本身也有一堆经验法则。最典型的是流水线深度流水线越深单级延迟越小主频可以拉高但分支预测失败时要清空更多级流水线惩罚也越大。所以主流高性能核的流水线深度通常压在一个十几到二十几级的区间不会无限加深再往上边际收益会被惩罚吞掉。乱序执行能提升 CPI尤其适合单线程重负载代价是功耗大、面积贵。低功耗场景更倾向于顺序核加简单分支预测牺牲峰值性能换能效。这也是为什么近几年的移动端和服务器端都不约而同走向“大小核”组合大核负责突发和高负载任务小核处理后台与轻量任务调度器按任务特性分配让整体落在能效最优的区间。这套设计思路背后没有新公式只是把功耗公式、执行时间公式和统计规律绑在一起做权衡。5. 把公式变成直觉一个完整对比分析的例子5.1 设定两个候选处理器备选方案前面讲了那么多规则与公式不如把它放到一个完整例子里跑一遍。假设某嵌入式产品要在两个候选处理器挑一个跑同一段基准程序指令数相同都是 10 亿条。候选方案主频平均CPI峰值功耗A2.6 GHz1.95 WB2.0 GHz1.38 W只看主频A 明显更高常识会告诉你 A 快。但我们用 CPU 时间公式算一下。5.2 用CPU时间公式和Amdahl定律逐层算账A 的执行时间TA 1e9 × 1.9 / 2.6e9 ≈ 0.731sB 的执行时间TB 1e9 × 1.3 / 2.0e9 0.65s结论很直接B 虽然主频低了 23%但因为平均 CPI 明显更好总执行时间反而比 A 快约 11%。如果当初只看主频做选型选 A 就等于选了一个更慢的方案。这一步已经足够说明主频只是除法式里的一个分母CPI 同样能决定结局。再算能耗用峰值功耗近似简化处理EA 5W × 0.731s ≈ 3.66J EB 8W × 0.65s ≈ 5.2JB 快但能耗高。如果你的设备是电池供电长期跑这种负载A 反而更耐用。性能选型永远要和具体目标绑定。接下来加一点 Amdahl 的味道假设 B 的快并不是全线生效而是只在 40% 的代码段上比 A 快 20%剩下 60% 两者表现相同。A 的总时间是 0.731s其中 40% 约为 0.292sB 在这部分只需 0.233s剩下 60% 是 0.439s。于是 B 整体时间变成 0.672s加速比只有 1.09明显小于直接用平均 CPI 估算的 0.65s 所对应的 1.12。更扎心的是上限即便 B 在那一部分做到无限快总体时间最多降到 0.439s加速比上限也只有 1.67 倍。局部优化的天花板一眼就看穿了。5.3 做这类分析时最常踩的几个坑第一只看主频。前面这个例子已经演示了高频和快之间没有必然关系。第二把峰值功耗当平均功耗。处理器负载是非平稳的DVFS 会让功耗随时间大幅波动。严谨做法是跑负载采集功耗曲线再对时间积分而不是拿一个标称峰值去乘总时间。第三Amdahl 里的 f 拍脑袋填。一定要基于 profiling 数据。f 填错加速比计算结果能差出好几倍整个决策跟着跑偏。第四跨指令集比较 MIPS。MIPS 在 ISA 不同时没有可比性连“谁每秒执行的指令更多”都说明不了更别说执行效率。第五忽略编译器差异。同一份源码同一颗处理器不同编译优化等级可能带来数倍性能差。做对比实验时务必固定工具链版本和优化选项否则得出的结论没法复现。5.4 随手可用的公式速查表最后把这些规则与公式汇成一张表方便以后做方案时快速查询。公式/规则形式主要用途常见误区CPU时间时间 指令数 × CPI × 周期时间处理器/编译器性能分析只看主频忽略CPIAmdahl定律加速比 1 / ((1-f) f/s)评估局部优化整体收益上限f凭感觉填不测profilingGustafson法则加速比 (1-f) N×f可扩展问题并行收益评估把可扩展问题误当固定问题MIPS主频 / (CPI × 10^6)教学、数量级估算跨ISA比较动态功耗P ≈ C × V² × f功耗/能效分析只降频不降压能耗没变能耗E P × t节能策略比较忽略时间维度这张表贴在我工位旁边已经很长时间了。每次有人拿着新方案来找我做性能评估我都会先把问题写成公式再填数最后才谈结论。规则和公式不会替你做决定但它们能逼你把分歧点精确到某一个变量上。团队里争吵不休的时候谁先把问题写成了式子谁通常就离正确答案更近一步。

相关新闻

体系结构核心公式与设计规则:从CPU性能方程到Amdahl定律

体系结构核心公式与设计规则:从CPU性能方程到Amdahl定律

做体系结构这行,大家工位上都离不开两样东西:设计规则和性能公式。规则负责划底线,告诉你哪些方向不能碰;公式负责算账,让你在动手之前就能把性能、功耗、面积这些数字估个大概。很多刚入行的朋友问我体系结构怎么上手…

2026/10/17 16:52:52 阅读更多 →
artcraft:让艺术资产可复用、可调试、可工程化的实践方法论

artcraft:让艺术资产可复用、可调试、可工程化的实践方法论

1. 项目概述:从“artcraft”这个词开始,我们到底在做什么?“artcraft”这个词乍一看像一个合成词,拆开是 art(艺术)和 craft(工艺),合起来直译就是“艺术工艺”&#xff…

2026/10/17 16:52:52 阅读更多 →
diagram-design:从手工绘图到可维护图表设计系统

diagram-design:从手工绘图到可维护图表设计系统

1. 从一张草图到一套系统:diagram-design 到底在解决什么问题第一次听到 “diagram-design” 这个词,很多人会下意识觉得它只是“画图”的另一种说法。但真正在项目里被图表折磨过的人知道,画图本身从来不是最痛的部分。最痛的是:…

2026/10/17 16:52:52 阅读更多 →

最新新闻

三相电信号采集电路设计:从互感器选型到ADC同步采样与相位校准

三相电信号采集电路设计:从互感器选型到ADC同步采样与相位校准

简介:《三相电信号采集电路设计》是一份面向电能质量监测、电力电子装置应用及供电系统运维等场景的技术资料,适合从事电网信号采集、嵌入式硬件设计的工程师与相关专业学生阅读。资源围绕三相电压电流信号的精密采集展开,涵盖互感器选型、频…

2026/10/17 19:27:40 阅读更多 →
SCAS与REPNE/REPE前缀全解:汇编字符串扫描的核心机制

SCAS与REPNE/REPE前缀全解:汇编字符串扫描的核心机制

写汇编的人都知道,字符串处理是这门语言最带劲的领域之一。而SCASB/SCASW这两条指令,配合REPNE/REPE重复前缀,能让你只用一条指令就把“在内存里找个字符 / 找个值 / 找到某个不匹配的位置”这种循环给干完。说实话,当年我第一次在…

2026/10/17 19:27:40 阅读更多 →
用友NC65安装全流程:Oracle表空间与数据源配置实战

用友NC65安装全流程:Oracle表空间与数据源配置实战

简介:这是一份面向用友NC65及NCC高版本实施顾问的完整安装操作手册,专为自学搭建测试环境的新手设计,能解决官网资料零散、上手门槛高的问题。资源为单个doc文档,压缩包仅933KB,便于携带和查阅,内容覆盖从O…

2026/10/17 19:27:40 阅读更多 →
SQL Server 2008误删恢复:简单模式下无备份数据抢救方案

SQL Server 2008误删恢复:简单模式下无备份数据抢救方案

简介:本资源是一份面向SQL Server数据库管理员与运维工程师的实战型数据恢复指南,聚焦SQL Server 2008环境下误删除数据的紧急抢救方案。内容系统梳理了基于事务日志恢复的三大典型场景:当具备完整备份且恢复模式为“完全(Full&am…

2026/10/17 19:27:40 阅读更多 →
DeepSeek Harness插件机制详解:从安装配置到开发实战

DeepSeek Harness插件机制详解:从安装配置到开发实战

1. 插件机制到底解决了什么问题1.1 从“一个能跑的壳”到“一个能干活的工具”DeepSeek Harness(后面我统一叫 dsh)刚装好的时候,它本质上就是一个能加载模型、能对话、能读本地文件的命令行壳子。你问它问题它能答,你让它读个文件…

2026/10/17 19:27:40 阅读更多 →
2026年最新全球AI大模型深度研究报告:用TaoToken统一Key实测主流模型能力边界

2026年最新全球AI大模型深度研究报告:用TaoToken统一Key实测主流模型能力边界

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 19:26:40 阅读更多 →

日新闻

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

简介:《DeepSeek使用方法.pdf》是一份面向DeepSeek入门及进阶用户的完整使用指南,重点围绕V3与R1两款模型的能力差异与适用场景展开,帮助读者根据任务类型与成本选择合适的模型。资源共1个PDF文件,压缩包大小约311KB,篇…

2026/10/17 0:00:25 阅读更多 →
200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

简介:这是一份聚焦DeepSeek提示词优化的《200Deepseek润色指令》PDF文档,共收录两百余条可直接套用的润色指令,涵盖语法修正、风格统一、逻辑梳理、表达精炼等维度,并针对商务写作、学术论文、广告文案等场景给出差异化建议。文档…

2026/10/17 0:00:25 阅读更多 →
OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

简介:本资源是一份基于OpenCV的本科课程设计完整报告,面向计算机视觉初学者、图像处理学习者及高校通信/信息类专业学生,解决物体智能计数与检测结果结构化记录的实际问题。项目覆盖图像预处理、轮廓检测、边界框绘制、轨迹栏交互式参数调节、…

2026/10/17 0:00:25 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/16 21:35:51 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/17 1:44:59 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/16 15:04:24 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 21:54:38 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 14:23:36 阅读更多 →