阅读视图

发现新文章,点击刷新页面。
🔲 ☆

从手写代码到日提 30 个 PR:Claude Code 缔造者的 AI 编程启示录

本文永久链接 – https://tonybai.com/2026/03/06/building-claude-code-with-boris-cherny

大家好,我是Tony Bai。

想象一下,你加入了一家全球顶级的 AI 实验室,满怀热情地提交了第一个 Pull Request (PR)。然而,你的 PR 却被直接拒绝了。原因不是代码写得不好,而是——这代码是你“手写”的

这不是科幻小说,这是 Boris Cherny 加入 Anthropic 时的真实经历。作为目前炙手可热的 AI 编程工具 Claude Code 的缔造者和工程负责人,Boris 曾是 Meta (前 Facebook) 最高产的程序员之一。但在 Opus 4.5 模型发布后,他的工作流发生了颠覆性的变化:现在,他每天可以提交 20 到 30 个 PR,且不再手动编辑任何一行代码。

近期的一期深度访谈中,Boris 分享了 Claude Code 从一个内部黑客项目到爆款工具的演进历程,以及他对于 AI 时代软件工程未来的深刻洞察。

Claude Code 的诞生:不要把 AI 关在盒子里

Claude Code 的前身是一个名为 “Clyde” 的内部原型。当 Boris 最初构思如何将 AI 融入编程时,他犯了一个很多开发者都会犯的错误:试图把 AI 当作系统中的一个组件。

在传统的思维模式下,我们倾向于把模型关在一个“盒子”里,为其定义严格的输入和输出接口(比如在 IDE 中高亮一段代码,然后让 AI 解释或补全)。但 Boris 很快意识到,这不是与大模型交互的正确方式。

“不要试图把它放进盒子里,不要强迫它以特定的方式行事。把模型看作一个独立的实体,给它工具,让它自己运行程序。”

这种被 Boris 称为“苦涩教训(Bitter Lesson)推论”的理念,成为了 Claude Code 的核心设计哲学。他赋予了模型执行 Bash 命令的权限,接着是读写文件系统的权限。当模型获得了与现实世界(操作系统)交互的能力后,奇迹发生了。

他举了一个早期的例子:他给了模型一个 Bash 工具,然后问它:“我正在听什么音乐?”模型竟然自己写了一段 AppleScript 脚本,调用 sed 等命令去查询本地的音乐播放器,并成功返回了答案。这一刻,Boris 感受到了真正的 AGI(通用人工智能)气息。

从手写到“指挥”:并行 Agent 的极致工作流

作为曾经 Meta 代码产量极高的工程师,Boris 现在的产出速度更是达到了令人咋舌的地步(每天 20-30 个 PR,从几行到几千行不等)。他是如何做到的?答案是大规模并行 Agent (Parallel Agents)。

他分享了自己目前极其硬核的终端工作流:

  1. 多开终端:在终端(如 tmux)中打开 5 个标签页,每个都是独立的代码库 Check-out(或者使用 Git Worktree)。
  2. 启动计划模式 (Plan Mode):在每个标签页中启动 Claude Code,并进入“计划模式”(按两次 Shift+Tab),向 Agent 描述需求。
  3. 轮询指挥:当第一个 Agent 开始思考和执行时,他立刻切换到第二个标签页启动另一个 Agent。如此循环。
  4. 验证与交付:当收到某个 Agent 完成任务的通知时,切回去检查结果。

在这种模式下,Boris 不再是一个“打字员”,而化身为一个“交响乐团指挥”。他的核心工作从“思考如何实现”,变成了“思考业务逻辑的类型签名(Type Signatures)”和“验证模型的输出”。

当 AI 编写了 80% 的代码,代码审查(Code Review)怎么做?

这是每个工程团队都会面临的灵魂拷问。在 Anthropic 内部,高达 80% 的代码现在由 Claude Code 生成。那么,他们是如何把控质量的?

答案是:用 AI 审查 AI,辅以人类的最后防线。

  1. Agent 自我测试:Claude Code 会在本地自动编写并运行测试。如果 Anthropic 工程师修改了 Claude Code 本身的源码,Agent 甚至会启动一个子进程来做端到端(E2E)测试。
  2. AI 初审 (Best of N):在 CI/CD 阶段,每一个 PR 都会先被 Claude 审查。为了解决 LLM 偶尔的非确定性和幻觉,他们采用了 Best of N 策略——启动多个并行的 Agent 进行审查,再用一个去重 Agent 汇总结果。这能拦截约 80% 的低级 Bug。
  3. 动态 Lint 规则:当发现同事的 PR 中出现了可被静态分析捕获的问题时,Boris 会直接要求 Claude 当场写一个 Lint 规则,从源头上杜绝此类问题。
  4. 人类拍板:尽管自动化程度极高,但对于企业级产品,目前 Anthropic 依然要求每个 PR 必须有真正的人类工程师进行第二轮审查并最终批准。

“我们就像 15 世纪的抄写员”

面对 AI 展现出的恐怖编程能力,即便是前特斯拉 AI 总监 Andrej Karpathy 也感叹自己“从未如此落后过”。许多程序员感到恐慌:我们寒窗苦读十载练就的编码技能,是不是要变成屠龙之技了(变得稀有且遥远)?

Boris 给出了一个非常精彩且充满希望的隐喻:印刷术的发明。

在 15 世纪印刷术出现之前,“识字和抄写”是极少数人的特权。他们被国王雇佣,经过多年训练才能胜任。而当时的许多国王,甚至自己都是文盲。

“我们现在的软件工程师,就像是那些抄写员。而业务方(CEO/PM)就像是那些不懂技术的国王。”

当印刷术出现后,书籍的成本下降了百倍,数量增加了万倍。抄写员并没有消失,他们变成了作家、编辑、出版商。随着识字率的普及,整个知识市场迎来了前所未有的大爆炸,催生了无数在那之前根本无法想象的职业和产业。

今天,AI 编程工具就是软件工程界的“印刷术”。编程的门槛正在被无限拉低,原本不懂代码的业务人员、设计师、财务人员(在 Anthropic 内部,非技术人员使用 Claude Code 的比例接近 100%)都能直接将想法转化为软件。这不会消灭软件工程,而是会让软件的产量和应用场景呈指数级爆发。

工程师的新生存法则:哪些技能在贬值,哪些在升值?

在这场范式转移中,作为开发者,我们需要对技能树进行重新评估。

正在快速贬值的技能:

  • 对语言和框架的宗教式狂热:不要再为“到底是用 React 还是 Vue”、“这应该用 Go 还是 Rust 写”而争得面红耳赤了。如果模型觉得当前框架不好,它随时可以用几分钟时间帮你用另一个语言重写一遍。
  • 沉溺于语法细节:未来将没有人再去手动敲击枯燥的样板代码。

愈发珍贵的核心能力:

  • 系统化与假设驱动思维:面对复杂的 Debug 场景,如何提出假设、逐步验证,这种科学的工程思维依然是 AI 目前难以完全替代的。
  • 跨界的好奇心:未来属于全栈通才。如果你懂前端、懂后端,同时还懂业务逻辑、设计心理学甚至财务模型,你就能借助 AI 工具,以“一人公司”的姿态构建出估值十亿美元的产品。
  • 高频上下文切换能力 (ADHD 式的工作法):在这个需要同时管理多个 AI 智能体的时代,不再那么强调长时间的“深度编码”,而是需要你能在多个高层上下文中快速穿梭、精准决策。

注:ADHD (注意力缺陷多动症) 式的工作法是一种灵活而高度分散注意力的工作风格,常常表现为多任务处理和非线性思维,能够快速切换多个任务并通过联想和直觉进行思考。这种方法倾向于将大的任务分解为小的、可管理的目标,以保持动力和成就感。同时,工作过程中的兴趣和关注点可能会快速变化,因此通常会采用短暂的工作间隔与休息时间。通过频繁调整和迭代的方式,ADHD式工作法能够帮助人们利用自身的优势,克服注意力集中的挑战。

小结:抛弃傲慢,拥抱变化

在采访的最后,Boris 坦言自己也经常感到挣扎:模型进化的速度太快了,几个月前验证失败的架构理念,换个新模型可能瞬间就跑通了。

在这个时代,“智力上的谦逊 (Intellectual Humility)” 比过往的经验更重要。不要再用旧时代的标尺去衡量新世界的工具。承认 AI 可能比你写得快、甚至写得好,放下作为“手写代码匠人”的骄傲,去学习如何更好地指挥这支由超级大脑组成的交响乐团吧。

毕竟,未来不属于那些拒绝使用 AI 的人,而是属于那些知道如何用 AI 构建下一个时代的人。

资料链接:https://www.youtube.com/watch?v=julbw1JuAz0


你敢交出“键盘”吗?

Boris 的经历让我们重新思考什么是“专业”。如果你提交的 PR 仅仅是因为“这是我手写的”而被拒绝,你的第一反应会是什么?在你的团队中,是否已经有人开始尝试这种“指挥家”式的工作流?

欢迎在评论区分享你的看法!


还在为“复制粘贴喂AI”而烦恼?我的新专栏 AI原生开发工作流实战 将带你:

  • 告别低效,重塑开发范式
  • 驾驭AI Agent(Claude Code),实现工作流自动化
  • 从“AI使用者”进化为规范驱动开发的“工作流指挥家”

扫描下方二维码,开启你的AI原生开发之旅。


你的Go技能,是否也卡在了“熟练”到“精通”的瓶颈期?

  • 想写出更地道、更健壮的Go代码,却总在细节上踩坑?
  • 渴望提升软件设计能力,驾驭复杂Go项目却缺乏章法?
  • 想打造生产级的Go服务,却在工程化实践中屡屡受挫?

继《Go语言第一课》后,我的《Go语言进阶课》终于在极客时间与大家见面了!

我的全新极客时间专栏 《Tony Bai·Go语言进阶课》就是为这样的你量身打造!30+讲硬核内容,带你夯实语法认知,提升设计思维,锻造工程实践能力,更有实战项目串讲。

目标只有一个:助你完成从“Go熟练工”到“Go专家”的蜕变! 现在就加入,让你的Go技能再上一个新台阶!


想系统学习Go,构建扎实的知识体系?

我的新书《Go语言第一课》是你的首选。源自2.4万人好评的极客时间专栏,内容全面升级,同步至Go 1.24。首发期有专属五折优惠,不到40元即可入手,扫码即可拥有这本300页的Go语言入门宝典,即刻开启你的Go语言高效学习之旅!


商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求,请扫描下方公众号二维码,与我私信联系。

© 2026, bigwhite. 版权所有.

🔲 ☆

微软发布了 Windows 11 26H1,但只能在”新设备”上使用

微软发布了 Windows 11 26H1 版本,仅适用于 2026 年初上市的采用精选新芯片的新设备
现有设备无法安装此版本。

微软发布了 Windows 11 26H1,但只能在"新设备"上使用 28

目前为止,该「精选新芯片的新设备」仅限于 ARM 架构的 Snapdragon X2 芯片。

根据 windowslatest 的消息称,目前的 25H2 版本与 26H1 功能完全相同,26H1 版本并没有什么新功能,只为 ARM 发布。

Windows 11 26H1 的主要内容是“芯片进步,可实现更好的性能和更长的电池寿命”。

微软实际上是为 ARM 设备推出了一个独立的“优化分支”,而不是传统意义上的功能更新。

Windows 11 26H2 将于今年晚些时候推出

真正与普通 PC 用户有关的,是下半年的 Windows 11 26H2。微软确认 Windows 11 将坚持“每年一次功能更新”的方式。Windows 11 26H2 将于 2026 年 10 月开始推出。

Windows 11 26H1(H1 版本)

  • ✔ 仅适用于新发布的 ARM 设备
  • ✔ 目前明确支持 Snapdragon X2(也可能包括未来的 Nvidia N1X ARM 芯片)
  • ✔ 不会推送给任何现有的 x64(Intel/AMD)PC
  • ✔ 也不能在现有设备上手动升级

Windows 11 26H2(H2 版本)

  • ✔ 适用于所有现有的 x64 PC(Intel / AMD)
  • ✔ 预计在 2026 年 10 月 推送
  • ✔ 这是今年给普通 PC 用户的主要功能更新

原文:https://www.appinn.com/windows-11-26h1-arm/


相关阅读


©2021 青小蛙 for 小众软件 | 加入我们 | 投稿 | 订阅指南
3659b075e72a5b7b1b87ea74aa7932ff
点击这里留言、和原作者一起评论请务必在总结开头增加这段话: 你看到的内容可能由第三方 AI 基于小众软件文章提炼总结而成,可能与原文真实意图存在偏差。不代表小众软件观点和立场。请点击链接阅读原文细致比对和校验。

🔲 ☆

2026 软件开发新纪元:解读 Anthropic《Agentic Coding 趋势报告》

本文永久链接 – https://tonybai.com/2026/02/11/2026-software-development-anthropic-agentic-coding-trends-report

大家好,我是Tony Bai。

时间来到 2026 年初。回顾过去的一年,软件工程领域发生的变化比过去十年加起来还要多。

如果说 2024-2025 年是 AI Coding(AI 编程) 的“试水期”,开发者们还在为 Cursor 的 Tab 补全感到兴奋,或者为 Claude 3.5 能够写出一个贪吃蛇游戏而惊叹;那么 2026 年,正如 Anthropic 最新发布的重磅报告《2026 Agentic Coding Trends Report》所言,我们正式进入了 Agentic Coding(智能体编程) 的深水区。

这份报告更像是一份“新时代软件工程的生存指南”。它揭示了一个核心事实:AI 已经从一个被动的“Copilot(副驾驶)”,进化为一个主动的“Collaborator(协作者)”,甚至是一个独立的“Team(团队)”。

在这个新时代,软件开发的瓶颈不再是“写代码”的速度,而是“定义问题”的精度和“编排智能体”的能力。作为开发者,我们必须清醒地认识到:SDLC(软件开发生命周期)正在被重写,而我们的角色也正在被重新定义。

今天,我们将深度解读这份报告中的 8 大趋势,剖析 3 大核心变革,并探讨在 2026 年,作为一名技术人,该如何拿到通往未来的船票。

地壳运动 —— 软件开发生命周期的彻底重构

Anthropic 报告的开篇就用“Tectonic Shift(地壳运动)”来形容正在发生的变化。这绝非夸张。

1. 抽象层级的再次跃迁

在计算机历史上,每一次抽象层级的提升,都带来了生产力的爆发:从机器码到汇编,从汇编到 C,从 C 到高级语言。

而在 2026 年,我们迎来了最新的抽象层:自然语言驱动的智能体编排。

报告指出,“写代码、调试、维护” 这些战术性的工作,正在全面转移给 AI。工程师的精力开始聚焦于架构设计、系统设计和战略决策。

这意味着,未来的“源码”,可能不再是 GitHub 仓库里那一堆 .ts 或 .go 文件,而是“Prompt + Spec(规规说明书) + Agent Configuration(智能体配置)”。

2. 入职(Onboarding)时间的坍塌

这是报告中一个极具冲击力的预测:“新员工入职一个复杂代码库的时间,将从数周缩短为数小时。”

还记得以前入职一家新公司,光是配置环境、阅读文档、理解那堆“屎山代码”的逻辑,就要花掉两周时间吗?

在 Agentic Coding 时代,像 Augment Code 这样的工具(报告案例),利用 Claude 对代码库的深度理解,可以让工程师在几分钟内获得对系统上下文的掌控。 此外,一位 CTO 预估需要 4-8 个月完成的项目,在 Claude Code的加持下,两周内就完成了。这是人力资源配置的革命。企业可以实现“动态激增(Surge)”式的人员调配,工程师可以随时在不同项目间无缝切换,而无需支付高昂的认知切换成本。

3. 工程师的“全栈化”

报告揭示了一个有趣的现象:AI 并没有取代工程师,而是让工程师变得更“全栈”了。

前端工程师开始敢于修改后端数据库,后端工程师也能轻松搞定复杂的 CSS 动画。为什么?因为 AI 填补了那部分“知识鸿沟”。

只要你具备系统思维和验收能力,具体的实现细节(Implementation Details)不再是障碍。这标志着“领域专家(Domain Expert)”与“通用工程师(Generalist)”的边界开始模糊。

能力跃迁 —— 从单体智能到“智能体集群”

如果说第一部分是“软性”的流程变化,那么第二部分则是“硬核”的技术能力升级。Anthropic 报告明确指出,2026 年的 AI 编码将呈现出集群化长时程的特征。

4. 单体 Agent 进化为 Coordinated Teams

2025 年,我们还在试图用一个超级 Agent 解决所有问题。2026 年,这种做法已经被淘汰。

报告预测:“多智能体系统(Multi-agent Systems)将取代单智能体工作流。”

  • 分工与协作:就像人类团队一样,我们需要“产品经理 Agent”拆解需求,“架构师 Agent”设计接口,“编码 Agent”写代码,“测试 Agent”找 Bug。
  • 并行推理:通过在不同的上下文窗口(Context Windows)中并行处理任务,效率实现了指数级增长。
  • 案例:劳动力管理平台 Fountain 使用 Claude 构建了分层的多智能体编排系统,将筛选速度提升了 50%。

5. 从“分钟级”任务到“周级”长跑

早期的 Agent 只能处理“帮我写个函数”这种几分钟的短任务。

但报告指出,Long-running Agents(长时运行智能体) 正在成为主流。

  • 时间跨度:Agent 可以连续工作数天甚至数周。
  • 自我管理:它们能够制定计划、迭代代码、从失败中恢复(Self-healing),并维护一致的状态。
  • 消除技术债:那些以前因为“太麻烦”而被搁置的重构任务、文档补全任务,现在可以丢给一个长时运行的 Agent,让它在后台慢慢跑,直到把 backlog 清空。

Rakuten 的案例令人印象深刻:工程师让 Claude Code 在一个拥有 1250 万行代码的开源库(vLLM)中实现一个复杂的数学算法。Claude 独自工作了 7 个小时,最终交付了准确率为 99.9% 的代码。

这就是“无人值守开发(Unattended Development)”的雏形。

6. 协作悖论:为什么我们还不能“完全放手”?

这部分是报告中最发人深省的洞察。

Anthropic 的社会影响研究团队发现了一个“协作悖论”

尽管工程师在 60% 的工作中使用了 AI,但他们报告称,能够“完全委托(Fully Delegate)”给 AI 的任务只有 0-20%。

这意味着 Human-in-the-loop(人类在环)依然是核心。

AI 不是那种“交给他就不管了”的外包,而是一个需要你持续关注、持续反馈的“实习生”或“副驾驶”。

  • 2026 的关键能力:智能体开始学会“求助(Ask for help)”。与其盲目猜测,不如在不确定时主动询问人类:“这里有两种设计方案,你倾向于哪一种?”
  • 监督的规模化:人类不再逐行审查代码,而是审查关键决策点和高风险边界。

行业冲击 —— 经济学与组织架构的重塑

技术变革必然引发经济变革。报告的第三部分探讨了 Agentic Coding 对商业世界的深远影响。

7. 软件开发的经济学重塑

传统的软件开发成本高昂,导致很多“小需求”或“长尾需求”无法被满足(ROI 算不过来)。

但 AI Agent 的出现,极大地降低了软件生产的边际成本。

  • Papercuts:那些让用户难受但又不值得花工程师时间去修的小 Bug,现在可以被 Agent 批量修复。
  • 产出量(Output Volume):生产力的提升不仅仅是“做得快”,更是“做得多”。企业可以尝试更多的实验,开发更多的定制化工具。
  • 案例:通信巨头 TELUS 的团队创建了 13,000 多个自定义 AI 解决方案,节省了 50 万小时的工作时间。

8. 编程能力的“民主化”与“下沉”

这是我认为最激动人心的趋势:Agentic Coding Expands to New Surfaces and Users.

  • 语言障碍消失:COBOL、Fortran 这些“古董语言”的维护不再是难题。AI 是最好的翻译官。
  • 非技术人员入场:销售、市场、法务团队,开始使用 Agent 构建自己的自动化工具。
  • 案例:法律科技平台 Legora 让不懂代码的律师也能利用 Claude Code 构建复杂的自动化工作流;Zapier 内部实现了 89% 的 AI 采用率,设计团队直接用 Claude Artifacts 进行原型开发。

“人人都是程序员” 的口号喊了很多年,但在 2026 年,依靠 Agent,这终于变成了现实。

9. 安全的双刃剑

当然,硬币总有两面。报告特别提到了 Dual-use Risk(双重用途风险)。

  • 防御侧:Agent 可以自动进行代码审计、漏洞扫描、安全加固。
  • 攻击侧:攻击者也可以利用 Agent 批量生成攻击脚本、寻找零日漏洞。

这要求我们在设计 Agentic System 时,必须将安全性(Security-first Architecture) 植入到基因中。

2026 年的行动指南 —— 优先事项

面对这些汹涌而来的趋势,作为技术决策者或一线开发者,我们在 2026 年应该做什么? Anthropic 给出了 4 个明确的优先事项:

  1. 掌握多智能体协作 (Master Multi-agent Coordination):不要再沉迷于优化单个 Prompt。去学习如何使用 Gas TownClaude Code 的 Agent Team 模式。学会如何让多个 Agent 像一支军队一样协同作战。这是解决复杂问题的唯一路径。

  2. 扩展人类的监督能力 (Scale Human Oversight):构建自动化审查系统。当 AI 一天生成 1 万行代码时,靠人眼看是看不过来的。你需要构建基于 AI 的 Reviewer,以及基于严格测试(Test-Driven)的验收流水线。

  3. 赋能领域专家 (Empower Domain Experts):不要把 AI 编程工具锁在技术部门。把它们分发给产品经理、法务、运营。让他们自己去构建解决问题的工具。

  4. 内嵌安全架构 (Embed Security Architecture):从第一天起,就要考虑 Agent 的权限边界。不要给 Agent 无限制的 sudo 权限。构建沙箱(Sandbox)鉴权机制

小结:拥抱“不确定性”的艺术

读完这份报告,我最大的感受是:软件工程正在从一门“精确的科学”,变成一门“管理的艺术”。

在 Software 1.0 时代,我们追求的是确定性,每一行代码的执行逻辑都是可预测的。

在 Agentic Coding 时代,我们管理的是概率,是模糊性,是一群有一定自主权但偶尔会犯错的数字员工。

这并没有让软件工程变简单,反而变得更难、更深刻了。

我们不再是代码的作者(Author),我们是代码的编辑(Editor)、导演(Director)和架构师(Architect)。

2026 年,对于那些愿意拥抱变化、主动升级认知模型的开发者来说,将是最好的时代。限制你产出的,不再是手速,而是你的想象力和领导力。

资料链接:https://resources.anthropic.com/hubfs/2026%20Agentic%20Coding%20Trends%20Report.pdf


你感到的是“解放”还是“威胁”?

Anthropic 预测 2026 年新员工入职代码库的时间将坍塌为几小时。在你目前的团队中,是否已经感受到了 AI 带来的这种“入职加速”?如果有一天你主要的工作变成了“编排 Agent 集群”,你觉得最大的挑战是什么?

欢迎在评论区分享你的 2026 职业预判!


如何落地 Anthropic 的预测?

趋势看懂了,但怎么落地?

  • 如何构建一个多智能体协作的代码重构流水线?
  • 如何实现长时程 Agent 的状态管理和断点续传?
  • 如何让非技术人员也能安全地使用 Coding Agent?

Anthropic 的报告指明了方向,而我的专栏负责提供地图和车辆

在我的极客时间专栏AI 原生开发工作流实战中,我们将深度对齐这份报告中的前沿技术:

  • 实战 Agent Team:复刻 Claude Code 的多智能体协作模式。
  • 安全与治理:学习如何为 Agent 构建安全护栏。
  • 构建自动化工厂的初步方案:打造基于 Spec 驱动的“无人值守”开发流。

不要做旧时代的守墓人,做新时代的领航者。扫描下方二维码,开启你的 Agentic Coding 之旅。


你的Go技能,是否也卡在了“熟练”到“精通”的瓶颈期?

  • 想写出更地道、更健壮的Go代码,却总在细节上踩坑?
  • 渴望提升软件设计能力,驾驭复杂Go项目却缺乏章法?
  • 想打造生产级的Go服务,却在工程化实践中屡屡受挫?

继《Go语言第一课》后,我的《Go语言进阶课》终于在极客时间与大家见面了!

我的全新极客时间专栏 《Tony Bai·Go语言进阶课》就是为这样的你量身打造!30+讲硬核内容,带你夯实语法认知,提升设计思维,锻造工程实践能力,更有实战项目串讲。

目标只有一个:助你完成从“Go熟练工”到“Go专家”的蜕变! 现在就加入,让你的Go技能再上一个新台阶!


商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求,请扫描下方公众号二维码,与我私信联系。

© 2026, bigwhite. 版权所有.

🔲 ☆

输入需求,输出系统:AI Agent 正在实现软件工程的“终极梦想” —— 软件工厂!

本文永久链接 – https://tonybai.com/2026/02/10/ai-agent-realizes-ultimate-dream-software-factory

大家好,我是Tony Bai。

在计算机科学与软件工程的历史长河中,始终存在着一个令人魂牵梦绕、却又屡屡受挫的终极梦想——“软件工厂(Software Factory)”

早在 20 世纪 60 年代,日本的大型科技企业(如日立、东芝)就开始尝试引入制造业的流水线理念来生产软件。80 年代,CASE(计算机辅助软件工程)工具试图实现全流程自动化;21 世纪初,MDA(模型驱动架构)试图通过 UML 图直接生成代码。

然而,这些尝试无一例外都未能成为主流。

为什么?因为软件开发与硬件制造有着本质的不同。硬件是标准化的,而软件需求充满了不确定性(Ambiguity)、非标准化(Non-standard)和创造性(Creativity)。传统的刚性流水线无法处理这种“软”的复杂性。

但这一次,不一样。

随着以 GPT-5.2、Claude 4.5、Gemini Pro 3.0 等为代表的大语言模型(LLM)能力的爆发,以Claude Code、Gemini Cli等编码智能体的快速演进,以及Agentic Workflow(智能体工作流)的成熟,我们第一次拥有了能够理解“非标需求”并将其转化为“标准代码”的通用推理引擎。

特斯拉前 AI 总监 Andrej Karpathy 将这一刻定义为 Software 3.0 的黎明。在这个新时代,那个尘封已久的“软件工厂”蓝图,正在从幻想变成触手可及的现实。

今天,我们就来深度剖析这座正在崛起的 AI 软件工厂,看看它将如何重塑我们的行业、生态与职业。

Software 3.0:从“写代码”到“定义目标”

要理解软件工厂的本质,我们需要先理解 Karpathy 提出的软件演进三阶段论。这是一次技术的迭代,更是编程范式的根本性迁移。

Software 1.0:显式编程 (Code)

这是我们最熟悉的时代。程序员使用 Go、Python、C++、Java、TypeScript 等语言,编写显式的逻辑规则。

  • 特征: 人类必须清楚地知道每一步该怎么做(How),然后翻译给机器。
  • 局限: 复杂度随着代码行数线性(甚至指数)增长,维护成本极高。这是典型的“手工作坊”模式。

Software 2.0:数据驱动 (Weights)

深度学习的兴起带来了 2.0 时代。程序员不再编写规则,而是编写目标(损失函数)和准备数据,由优化器(Optimizer)在神经网络的权重空间中搜索出最优解。这是一个黑盒。虽然它能解决图像识别等 1.0 很难解决的问题,但它缺乏逻辑的可解释性。

Software 3.0:自然语言编程 (Prompts)

现在,我们进入了 3.0 时代。LLM 成为了一个新的、通用的可编程实体。

  • 特征: 编程语言变成了英语(或任何自然语言)。我们不再需要告诉机器“怎么做(How)”,只需要告诉它“做什么(What)”和“想要什么结果(Goal)”。
  • 质变: Prompt 成了新的源代码。而能够理解 Prompt 并执行任务的 AI Agent,成了新时代的工人。

正是 Software 3.0 的出现,让“输入模糊需求,输出精确系统”成为了可能。

全景图:解构一座柔性的“AI 软件工厂”

想象一下,未来的软件交付不再是一个团队几周的冲刺,而是一个工厂几分钟的运转。这座工厂不再由传送带和机械臂组成,而是由运行在云端的 Agent Swarm(智能体集群)构成。

这是一座柔性制造的超级工厂,其运作流程如下:

输入端:非结构化的意图

你不需要编写代码,甚至不需要编写格式严格的 PRD 文档。

工厂的原材料可以是极其粗糙的:

  • 一段 30 分钟的产品会议录音。
  • 一张在白板上画的草图照片。
  • 或者仅仅是一句模糊的想法:“帮我做一个给宠物猫记账的小程序,要能识别猫粮的发票,还要有月度支出的数据看板。”

生产车间:智能体协作网络

需求进入工厂后,会被一个Orchestrator(编排器)接管,并分发给不同的“职能车间”。这些车间由专精不同领域的 Agent 组成:

  • 设计车间 (Architect Agent):
    它首先分析需求,进行系统拆解。它会输出:

    • API Spec: 定义前后端交互的接口(如 OpenAPI/Swagger)。
    • Database Schema: 设计数据库表结构(如 SQL DDL)。
    • Tech Stack: 根据需求量级选择技术栈(是选 Next.js 还是纯 HTML?)。
  • 制造车间 (Coder Agent):
    这是工厂的主力军。它会裂变出多个子 Agent 并行工作:

    • Frontend Agent: 根据设计稿生成 React 组件。
    • Backend Agent: 编写 API 逻辑和数据库访问层。
    • SQL Agent: 编写复杂的查询语句。
      它们通过 GitHub 或共享文件系统进行协作,像真实团队一样提交 Pull Request。
  • 质检车间 (QA Agent):
    这是保证“良品率”的关键。QA Agent 不会等到代码写完才介入,而是采用 TDD(测试驱动开发)模式。

    • 它会先根据 Spec 生成测试用例(Test Cases)。
    • 然后对 Coder Agent 提交的代码进行“红绿循环 (Red-Green Loop)”测试。
    • 如果发现 Bug,它不会只是报错,而是将错误日志作为“反馈信号”退回给制造车间,要求重做。
  • 装配车间 (DevOps Agent):
    代码通过测试后,DevOps Agent 上场。它编写 Terraform 或 Dockerfile,调用 AWS/Aliyun/Cloudflare的 API,自动配置云端环境,进行部署。

输出端:即时可用的服务

工厂的传送带末端,输出的不是一堆冷冰冰的代码文件,而是一个可访问的 URL,一个已经配置好的 Admin 后台,以及一套完善的系统监控仪表盘。

这就是 Software 3.0 的终极形态:Prompt in, System out.

核心变革:柔性制造与动态编排

为什么我们要强调这是“柔性”工厂?因为它解决的是传统 CI/CD 流水线最大的痛点——刚性。

传统的流水线是线性的(Build -> Test -> Deploy)。一旦 Test 挂了,流水线就停了,红灯亮起,必须等待人类工程师介入修 Bug。

但 AI 软件工厂是有生命、会呼吸的。

它是基于 Agentic Workflow 的动态有向无环图(DAG),甚至是包含循环的图。

  • 自愈 (Self-Healing):当 QA Agent 发现 Bug 时,系统不会停机。它会触发一个“修复循环”。Coder Agent 会分析 QA 给出的错误日志,结合源代码进行推理,修改代码,再次提交。这个过程可能在几秒钟内迭代数十次,直到测试通过。
  • 动态扩容 (Dynamic Scaling):如果 Architect Agent 发现需求特别复杂(比如涉及 50 个页面),它会自动指挥工厂“招人”——即启动更多的 Coder Agent 实例并行开发,最后再进行合并。

这是一条会思考的流水线。它不仅生产代码,还生产基础设施(IaC)。它与云厂商深度集成,实现了真正的 Serverless——作为用户,你连 Server 都不用感知,你只感知 Service。

行业震荡:生态与角色的重构

当这种“输入需求,输出系统”的工厂模式普及后,软件行业的格局将发生天翻地覆的变化。

软件工程:从“人际协作”到“机机协议”

传统的软件工程理论(Agile, Scrum, 看板)很大程度上是为了解决“人与人协作”中的摩擦——信息不对称、理解偏差、情绪波动。

但在 AI 工厂里,协作变成了 A2A (Agent-to-Agent) 的协议交互。

  • Agent 不会开小差。
  • Agent 不会误解符合 Spec 的接口定义。
  • Agent 不需要每日站会同步进度。

未来的软件工程,将从管理“人”,转向管理“协议”和“标准”。协作的重心将聚焦于“人与工厂”的交互——即如何更精准、更高效地向工厂下达指令(Prompt Engineering / Spec Writing)。

软件生态:开源项目的“模具化”

在 Software 1.0 时代,开源项目(如 React, Spring, Django)是给人用的库(Library)。我们需要学习它的文档,理解它的 API。

在 Software 3.0 时代,开源项目将变成工厂的“模具”。

我们可能不再直接引用库,而是告诉工厂:“用 React 的模具生产前端”。源代码(Source Code)本身可能会变成像汇编语言一样的中间产物/表示——只有 AI 读写它们,而人类只会面向Spec。

Software is ephemeral. Spec is eternal.(软件是瞬态的,规格是永恒的。)

从业者:从“码农”到“厂长”

这是最残酷但也最充满机遇的转变。软件公司的人才结构将呈现极端的两极分化:

  • 订货人 (Product Owner / PM):
    负责定义“我要什么”。在工厂时代,生产成本趋近于零,“决策”的成本变得极高。你需要极强的业务洞察力、审美能力和用户同理心。因为工厂生产得太快了,如果你不知道什么是好的,你生产出来的就是一堆垃圾。
  • 厂长 (Platform Engineer / Architect):
    负责维护工厂本身。你需要设计 Agent 之间的协作 SOP,优化工厂的“良品率”,集成最新的模型能力,确保工厂不会生产出有安全漏洞的产品。
  • 消失的角色:
    纯粹的、重复性的“代码搬运工”和初级 CRUD 工程师。他们的工作将完全被 Coder Agent 取代。

小结:工业革命的前夜

我们正处于软件行业“手工作坊”向“机器大工业”过渡的前夜,就像 1760 年代瓦特改良蒸汽机的前夜。

AI 软件工厂 不是科幻小说,它正在此时此刻发生。

Claude Code的Agent Team、针对编码智能体编排的Gas Town等,很可能都是这座工厂雏形的组件。

Karpathy 说的 “The hottest new programming language is English” 并不是一句玩笑。它意味着编程的门槛被无限降低,但构建系统的门槛被无限拔高。

无论你是想做“订货人”还是“厂长”,现在开始学习驾驭 AI Agent,学习如何构建和管理这些“数字员工”,是你拿到新时代船票的唯一方式。


你的“厂长”初体验

“软件工厂”的时代正在加速到来,我们每个人都将面临从“码农”到“订货人”或“厂长”的转型。想象一下,如果你现在拥有一座 24 小时不停工的“AI 软件工厂”,你最想让它为你生产一个什么样的系统?你认为在“机机协作”的未来,人类程序员最后的护城河在哪里?

欢迎在评论区分享你的脑洞与思考!让我们一起在这场软件工业革命的前夜寻找坐标。

如果这篇文章为你揭示了软件工程的未来,别忘了点个【赞】和【在看】,并转发给你的架构师朋友,大家一起未雨绸缪!


亲手搭建你的“微型工厂”

虽然完全自动化的“软件工厂”还在建设中,但其中的核心技术——Agent 编排、Spec 驱动开发——已经触手可及。

在我的极客时间专栏《AI 原生开发工作流实战》中,我将带你从零开始,利用 Claude Code,构建一个微型的 AI 软件流水线。

  • 如何让 Coder Agent 和 QA Agent 左右互搏,实现代码自愈?
  • 如何用 Spec 文档指挥整个生产流程?
  • 如何构建一个能自我修复 Bug 的智能体闭环?

扫描下方二维码,开启你的 AI 架构师之旅。


你的Go技能,是否也卡在了“熟练”到“精通”的瓶颈期?

  • 想写出更地道、更健壮的Go代码,却总在细节上踩坑?
  • 渴望提升软件设计能力,驾驭复杂Go项目却缺乏章法?
  • 想打造生产级的Go服务,却在工程化实践中屡屡受挫?

继《Go语言第一课》后,我的《Go语言进阶课》终于在极客时间与大家见面了!

我的全新极客时间专栏 《Tony Bai·Go语言进阶课》就是为这样的你量身打造!30+讲硬核内容,带你夯实语法认知,提升设计思维,锻造工程实践能力,更有实战项目串讲。

目标只有一个:助你完成从“Go熟练工”到“Go专家”的蜕变! 现在就加入,让你的Go技能再上一个新台阶!


商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求,请扫描下方公众号二维码,与我私信联系。

© 2026, bigwhite. 版权所有.

🔲 ☆

Rust 输了?在 AI Agent 的战场上,TypeScript 才是唯一的“神”

本文永久链接 – https://tonybai.com/2026/01/31/rust-vs-typescript-ai-agent-battleground-winner

大家好,我是Tony Bai。

如果把 2025 年定义为 Coding Agent(编程智能体) 的元年,那么刚刚开启的 2026 年,毫无疑问是 Personal AI Agent(个人助理智能体) 的元年。

openclaw(曾用名Clawdbot/Moltbot)为代表的开源项目,一夜之间席卷了 GitHub,让无数开发者为之疯狂。但在这一片繁荣的景象背后,作为一名敏锐的技术观察者,我发现了一个极其有趣的现象。

请环顾四周,看看那些最顶尖、最流行、体验最好的 AI Agent 项目:

  • Claude Code (Anthropic 官方):TypeScript。
  • Gemini CLI (Google 官方):TypeScript。
  • openclaw(100k+ Star):TypeScript。
  • opencode以及配套的oh-my-opencode:TypeScript。

再看看Go语言,虽然没有占据头把交椅,但也稳稳地守住了一席之地。Gastowncrush 这些专注于并发和后端服务的 Agent 或Agent编排框架,依然拥有自己的一批拥趸。

但是,那个在过去几年呼声最高、号称“内存安全、性能无敌、将重写一切”的 Rust 去哪了?

在 AI Agent 的应用层战场上,尤其是像上述这些火出圈的AI Agent项目中,Rust 几乎“失声”了。除了 OpenAI 的 Codex 这个孤勇者之外,我们很难在主流的开源 Agent 列表中看到 Rust 的身影。

难道在 AI 时代的Agentic AI(智能体AI应用)阶段,Rust 输了吗?为什么被视作“玩具语言”的 TypeScript,反而成了 AI Agent的“母语”?

今天,我们不谈信仰,只谈架构。让我们深入剖析这场语言战争背后的第一性原理。

数据说话:统治 Agent 界的“TS 军团”

在下结论之前,我们先来看一组数据。

我统计了目前 GitHub Trending 上排名前 20 的 AI Agent 相关项目(排除单纯的模型推理框架,仅统计应用层 Agent),结果令人震惊:

  • TypeScript / JavaScript:占比约 75%。
    这是绝对的统治地位。无论是官方的 SDK,还是社区的野生项目,TS 都是默认选项。openclaw的作者 Peter Steinberger 本人就是 iOS 和 C++ 出身,但他依然选择了 TS 来构建他的个人AI助理。
  • Python:占比约 15%。
    依靠着 LangChain 和 AutoGen 的早期积累,Python 依然有存量优势,但在构建交互式 CLI全栈应用 时,Python 的体验明显不如 TS 丝滑。
  • Go:占比约 8%。
    Go 凭借其单文件分发(Single Binary)和强大的并发模型,在Agent编排框架、Coding Agent,尤其是 DevOps 类的 Agent(如 K8s 运维助手)中表现亮眼。
  • Rust:占比 < 2%。
    除了 OpenAI 这种拥有无限工程资源的巨头敢用 Rust 重写 Codex,绝大多数独立开发者和创业公司似乎都对其敬而远之。

这个数据说明了什么?

说明在 Agent 这个特定的垂直领域,开发效率(Velocity) 已经彻底压倒了 运行效率(Performance)

对于一个每秒钟只能输出 50 个 Token 的 LLM 来说,你的程序是 1ms 响应还是 10ms 响应,用户根本感觉不到区别。但你能否在 1 天内上线一个新功能,用户感知极强。

第一性原理:为什么是 TypeScript?

TypeScript 之所以能赢,绝不是因为运气,而是因为它在基因层面契合了 AI Agent 的特性。

AI 的“母语”是 JSON,而 TS 是 JSON 的亲兄弟

这是最核心的原因之一。

大模型(LLM)与外部世界交互的通用协议是什么?是 JSON

无论是 Tool Calling(函数调用),还是 Structured Output(结构化输出),LLM 吐出来的都是 JSON。

  • TypeScript: 处理 JSON 是原生的。JSON.parse() 之后,直接当作对象操作。配合 TypeScript 的 Interface 定义,你可以获得极佳的类型提示,但又保留了运行时的灵活性。

    // TS: 轻松处理
    interface ToolCall { name: string; args: any }
    const call = JSON.parse(llmOutput) as ToolCall;
    
  • Rust/Go: 你需要定义严格的 Struct。如果 LLM 发疯,多返回了一个字段,或者把 int 写成了 string,你的 serde_json 或 json.Unmarshal 就会直接报错 panic。在 AI 开发中,你需要的是“宽容”,而 Rust/Go 给你的却是“严厉”。

“Vibe Coding” 需要松弛感

openclaw 作者提到的 Vibe Coding,本质上是一种“心流状态”。我想到了一个功能,告诉 AI,AI 生成代码,我运行,成功。整个过程行云流水。

  • TS 的体验: AI 生成了一段 TS 代码,可能类型有点小问题,用了 any,但能跑。我先跑起来看看效果,以后再修类型。It works > It is correct.
  • Rust 的体验: AI 生成了一段 Rust 代码。10分钟后编译器报错:“生命周期不匹配”、“借用检查失败”、“unwrap 可能会 panic”。你被迫停下来,花 30 分钟和编译器搏斗。你的 Vibe(氛围)瞬间没了。

在探索性开发(Exploratory Development)阶段,Rust 的严格性变成了阻碍。

生态位的降维打击:全栈与浏览器

Agent 不仅仅是在终端跑。它需要操作浏览器(比如使用Playwright),需要写 Chrome 插件,需要构建 Web UI。

在这些领域,TS 是唯一的王

如果你的 Agent 需要抓取网页数据,TS 有最成熟的库;如果你的 Agent 需要提供一个可视化的 Dashboard,TS 前后端通吃。

Rust 的尴尬与反击:退守“基础设施”

那么,Rust 真的输了吗?

从应用层来看,是的。但从基础设施层来看,Rust 依然是基石。

我们必须看清一个分层结构:

  • L0 (Infrastructure): 向量数据库 (LanceDB, Qdrant)、推理引擎 (像Candle)、高性能网关。这是 Rust 的领地。
  • L1 (Application): Agent 业务逻辑、流程编排、工具调用。这是 TypeScript 的领地。

Rust 并没有输,它只是退到了幕后。 Rust 成了 AI 的“地基”之一,而 TS 成了 AI 的“胶水”。

Agent 本质上就是把 LLM、数据库(记忆)、API 粘合在一起的胶水层。在这个层面上,灵活的胶水(TS)永远比坚硬的水泥(Rust)好用。

Go 的中间路线:CLI 界的“钉子户”

在这场战争中,Go 语言处于一个非常有趣的位置。它不像 TS 那么动态,也不像 Rust 那么死板。

Go 在 Agent 领域依然有一席之地,主要得益于两点:

  1. Single Binary (单文件分发):
    如果你写一个 CLI Agent 分发给用户,Go 编译出来就是一个二进制文件,扔过去就能跑。TS 还需要用户装 Node.js,装依赖(npm install 地狱)。对于 openclaw 这种本地工具,其实 Go 也是一个极佳的选择(虽然作者选了 TS)。
  2. 并发模型 (Goroutine):
    当我们需要构建 Agent Swarm (蜂群),比如同时启动 100 个 Agent 去爬取数据、分析情报时,Go 的 Goroutine 模型比 TS 的 Promise.all 更轻量、更可控,性能也更佳。

像 Beads 和 Gastown 这样的项目选择 Go,正是看中了它在工程化和并发上的平衡。

小结:语言没有优劣,只有“生态位”

Openclaw 的爆火和 Claude Code 的选择,向我们揭示了 AI 时代的一个新真理:

在 Agent 应用层,灵活性(Flexibility)和容错性(Forgiveness)是第一生产力。

  • 如果你想快速构建一个能够“听懂人话、调用工具”的 Agent,请毫不犹豫地选择 TypeScript
  • 如果你想构建一个高性能的 llm 路由网关、MCP Server 或 并发Agent编排工具,又或是Cli Agent,Go 是你不错的好帮手。
  • 如果你想造一个新的 向量数据库推理引擎,请拥抱 Rust

不要带着旧时代的“语言鄙视链”进入新时代。

在 AI 眼里,代码只是它实现目标的工具。它写 TS 最顺手,那 TS 就是最好的语言。

Rust 没有输,它只是太“硬”了,不适合在这个充满幻觉和不确定性的 Agent 世界里跳舞。


你的“Agent 母语”

TypeScript 的统治力看似不可动摇,但技术圈永远不缺变数。在你心目中,开发 AI Agent 的最佳语言是哪一门?你愿意为了开发效率而忍受 TypeScript 的类型体操,还是为了极致性能去啃 Rust 的硬骨头?

欢迎在评论区捍卫你的“本命语言”!让我们看看谁才是真正的 Agent 之王。

如果这篇文章颠覆了你的技术选型观,别忘了点个【赞】和【在看】,并转发给还在纠结学什么的兄弟!


还在为“复制粘贴喂AI”而烦恼?我的新专栏 AI原生开发工作流实战 将带你:

  • 告别低效,重塑开发范式
  • 驾驭AI Agent(Claude Code),实现工作流自动化
  • 从“AI使用者”进化为规范驱动开发的“工作流指挥家”

扫描下方二维码,开启你的AI原生开发之旅。


你的Go技能,是否也卡在了“熟练”到“精通”的瓶颈期?

  • 想写出更地道、更健壮的Go代码,却总在细节上踩坑?
  • 渴望提升软件设计能力,驾驭复杂Go项目却缺乏章法?
  • 想打造生产级的Go服务,却在工程化实践中屡屡受挫?

继《Go语言第一课》后,我的《Go语言进阶课》终于在极客时间与大家见面了!

我的全新极客时间专栏 《Tony Bai·Go语言进阶课》就是为这样的你量身打造!30+讲硬核内容,带你夯实语法认知,提升设计思维,锻造工程实践能力,更有实战项目串讲。

目标只有一个:助你完成从“Go熟练工”到“Go专家”的蜕变! 现在就加入,让你的Go技能再上一个新台阶!


商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求,请扫描下方公众号二维码,与我私信联系。

© 2026, bigwhite. 版权所有.

🔲 ☆

Clawdbot深度评测:全能AI助理的成本与实战避坑

一个复古机器人Clawdbot站在堆叠的Mac Mini主机上,周围环绕着爆炸式的社交媒体点赞图标,羊皮纸,钢笔彩色手绘的统一风格。

Clawdbot AI再进化,社交媒体又爆了。这一次是真的很厉害,还是尬吹?

大家好,欢迎收听老范讲故事的YouTube频道。被突然爆火的clawdbot给砸到头了,这是一种什么样的感觉?

我最近在X上面,看到很多人在晒他们新买的Mac mini,甚至有人晒了12台的Mac mini,摆满了办公桌。实在让我觉得很诧异,他们到底在干什么?后边都有一个词叫clawdbot。我一开始还没有太注意这件事情,昨天直播的时候有人问我:“最近最火的clawdbot你玩了没有?”哎呀我还没玩儿,因为最近在玩agent skills,还没有太关注到。这么神奇的东西我要去看一看。

突然爆火的原因

一个拟人化的AI小助手坐在办公桌前,旁边日历显示24小时工作,桌角有一堆正在燃烧的金币代表Token消耗,羊皮纸,钢笔彩色手绘的统一风格。

突然爆火的原因,是因为很多人跑出来吹了,说这个东西实在是太强了,又革命性了。2025年11月25日,这个产品就已经上线了,它是个开源产品,上线在GitHub上面。到2026年的1月,突然有很多位的网红博主开始非常用力的宣传这个产品,一下就火出圈了。这是一个住进聊天软件里面、7*24小时服务的助理,甚至有很多人给这个助理直接起了个名字。

大家要注意,我们一般不会给ChatGPT、Gemini或者是Anthropic Claude起名字,而像现在的这个clawdbot,很多人都给他们起名字了。这是一个非常非常划时代的事情,因为你一旦给它起名字了,它就人格化了。这不是那种情感陪伴型的聊天工具,这是一个帮你去办公的助理,这是非常重要的。这帮网络大V就出来吹了,说这是个人AI助理的未来形态。有人一周烧掉了1.8亿TOKEN。大家注意,这是非常关键的一个信息:使用clawdbot,你的TOKEN在燃烧

产品形态与体验的明显差异

1. 全时驻留

台Mac Mini安放在舒适的家居办公桌上,连接着漂浮的Word文档、Excel表格和iMessage聊天气泡,羊皮纸,钢笔彩色手绘的统一风格。

它的产品形态跟体验上,跟过去的产品有明显的差异。第一个特别重要的差异,就是全时驻留。像以前我要去跟ChatGPT聊天,我要点开APP,或者我要到网站上去打开这个网页;现在这个就不用了,它就永远在线,而且功能非常完整。这也是为什么Mac mini突然销量暴增的原因。你可以命令它:

  • 打开Word文档
  • 打开Excel
  • 去干任何事情,浏览网站
  • 用iMessage跟人聊天、给人发短信

它这个全能干,没有任何问题。但是你要保证所有功能都能使的话,特别是你要使用iMessage的话,你必须要有Mac的系统,要有Mac、要有Mac mini。这是一个自托管成本很低、部署很方便的系统。大家都是买个Mac mini放在家里头,甭管是放在办公桌上,还是放在机柜里,放在电视旁边,这都不重要。但是这是你放在自己家里头的,你不用再担心任何隐私问题了。

2. 持久记忆加上主动触达

AI助手手里拿着厚厚的记事本,正在主动轻拍一位正在休息的用户的肩膀进行提醒,背景充满记忆碎片,羊皮纸,钢笔彩色手绘的统一风格。

像我们现在都说ChatGPT也好,一些聊天工具也好,要有记忆,但是他们记住的东西其实非常少。原因也很简单,如果ChatGPT记住很多东西的话,他就不知道什么时候该用什么了。而现在的clawdbot他是全记忆,你跟他聊天的所有内容他都记得。

所以很多的博主上来用clawdbot之前,先会用很长的时间去跟他描述:

  • 我是谁
  • 我喜欢什么东西
  • 我在干什么
  • 我对什么东西感兴趣
  • 我对什么东西有什么样的要求
  • 为什么我喜欢这个球队、为什么我喜欢那首歌……

他会把这些东西通通都告诉这个clawdbot。他会记下来,记下完了以后再去跟你聊天的时候,这些通通都会变成系统资料,它就会很懂你。

而且clawdbot还有一点非常重要的是什么?就是它会主动的来去跟你聊天。原来是被动的,你不去跟ChatGPT说话,它就不会回答你任何问题,所以我们要先提出问题。而现在的话,你可以告诉他说:“什么什么时候记得提醒我干事”、“每天告诉我最近应该做一些什么什么样的事情”。不是说你列好计划让他做什么事情,而是说你觉得我应该做点什么,他会告诉你说我觉得你应该干点这个、应该干点那个,他会有很多这样的建议性的东西出来。甚至他每天早上起来说:“我今天早上起来了,把我认为你今天该干的活都给你列出来。”他可以干这样的事情。

开源且无所不能的系统

这个系统还是开源的,而且迭代的速度非常快。之所以突然爆火,还有一个很重要的原因,就是这个产品基本上是无所不能。你基本上能想到的活它全能干,包括你让它去做vibe coding,你让它指挥Claude code下去干活去,都没问题。它可以浏览各种网页替你买东西,通过agent skills和这个MCP,我们现在互联网上这些服务,它全都可以使用起来了。就是因为这些原因,这个产品突然就爆起来了。

但是你说这个里头有没有尬吹的部分?肯定有。你自己去安装的时候,你就会发现可能也没那么方便。而且如果有些人对于结果的格式要求非常严格的话,你可能会觉得他产出的东西依然是AI垃圾。但是方向是正确的,就是全时驻留、持久记忆、主动触达,这就是未来的AI助理的一个方向,而且还要最好能够全能一些,所有问题都可以解决掉。

有人说原来ChatGPT不是出过这种东西吗?原来ChatGPT你是可以通过WhatsApp跟他聊天的,为什么到这就突然爆了?因为很简单,ChatGPT你虽然可以通过WhatsApp跟他聊天,但是它只能调用ChatGPT里边这些东西。你说我想去调用外边这些东西,我想去写个Word文档、我想去做个PPT、我想去剪个视频、我想去搜集一些信息,它这个功能还是有一定局限的。他们家就是玩这套东西,所以就并没有推开。而现在clawdbot直接就爆了。

传奇的创始人:Peter Steinberger

一位睿智的程序员肖像,背景融合了PDF文档结构图和维也纳的建筑剪影,羊皮纸,钢笔彩色手绘的统一风格。

它的创始人很传奇,这个创始人的经历还让老范很有代入感,为什么?这哥们在维也纳是一位退休程序员。老范现在也可以算退休程序员了,但是人家还是比我厉害很多了。这个人叫Peter Steinberger,他是PSPDFKit的创始人。这个产品是什么?是面向开发者的PDF的SDK框架。它给你一套框架,然后你可以写程序,通过它这套框架去操作PDF,做PDF查看、PDF注释、PDF编辑、签署、填表单,做这些功能。它的产品在iOS、安卓、Web和桌面端全覆盖。它的公司主要是提供文档、PDF相关的SDK和框架能力的。因为它有这样的一个技术背景,所以对于配置系统、跨平台交付、可观测行为、安全边界等等这些方面,都是非常敏感的。这也是为什么clawdbot这样的一个产品突然会爆起来。

Clawdbot到底能干点什么?

什么都能干。就是这么简单的一个问题。但是你说真的什么都能干嘛?跟大家讲一个笑话。岳云鹏有一次出去参加综艺,人家问他你数学怎么样?说特别快没问题。然后就出了一个问题:

26*78等于多少?等于75。

人家说你这对不?

岳云鹏说:“我又没说我算的特别对,我就说我算的特别快,你就说我快不快吧?”

所以虽然clawdbot什么都能做,但是结果到底是不是能够让人满意,就是冷暖自知了。有些人很挑剔,他就觉得这不行;这些人可能提的问题也很模糊,对于结果又很挑剔,那么他就得不到满意的结果。有些人的问题提的非常详细、非常具体,对于结果特别是格式又要求不是很高,他们就会得到满意的结果。我觉得这样解释是相对比较清楚的。那种提问题、提要求的时候云山雾罩,经常玩这个“佛祖拈花一笑”,出来的这个结果还挑三拣四的这种领导,反正伺候起来比较难吧。比较难伺候的领导,clawdbot这样的助理他也搞不定。但是有一些领导就是提要求事无巨细,只要结果正确、格式无所谓的,这些领导,clawdbot就是你最好的助理。

部署Clawdbot的风险:TOKEN在燃烧

一个巨大的沙漏,里面的沙子是金币,正在快速流逝,象征昂贵的Token消耗,羊皮纸,钢笔彩色手绘的统一风格。

但是如果你去部署clawdbot,一定要小心的是什么?TOKEN在燃烧。前面有人一个礼拜烧了1.7亿TOKEN,那是非常非常贵的。通常使用clawdbot需要什么?就是买Anthropic Claude 4.5 Opus 200美金一个月的Max账号。如果没有这个账号的话,这个产品会很难用的。当然了现在我们就在看Anthropic会不会封他,因为前不久Anthropic刚刚把open code的账号给封了。原来我们使用open code的时候,也可以用Anthropic的20美金或者200美金的这种Pro或者是Max账号,但是Anthropic说不行,不让你用了。所以现在还要看,它到底能使到哪天。

千万千万不要干嘛?千万不要用Anthropic的API key,你真的会破产的。那个玩意非常非常的消耗TOKEN。GPT 5.2据说也还不错,但是跟Anthropic的Claude 4.5 Opus还是有一点点差距的,最好也是用200美金的Pro账号。用我现在这种plus账号可能是比较费劲的,我准备待会把它装上,把plus账号挂上试试。还有博主推荐Mini Max,Mini Max有10美金左右的月账号,它也是一种编程账号,效果再比open code再差一些,但是人便宜。大家也可以试一试。功能都是TOKEN烧出来的,你没有那么多TOKEN,就不要指望它有那么多功能。

为什么一定要Mac mini?

一个架子上整齐排列着多台Mac Mini组成的家庭服务器农场,指示灯闪烁,羊皮纸,钢笔彩色手绘的统一风格。

这么多人都去晒Mac mini,其实并不是必须要Mac mini,最好是使用闲置的非工作主力电脑。你说我这就是上班每天用的电脑,我把这个clawdbot挂在上头行不行?最好别这么干。为什么?因为你上班的电脑第一个,它的能力很强,晚上有可能还会关机,比如说你要把它合起来,这个电脑就会关掉。这个系统是要7*24小时工作的,所以你最好不要把它放在你的工作电脑上。很多家庭有这种闲置的Mac mini,放这个上面就挺方便的。价格也不贵,也还很省电,还很漂亮。特别是最新的Mac mini M4,很小、非常非常漂亮、非常精巧,放在家里头、放在各种地方都不显得突兀。

全功能的系统配置

最好是给clawdbot配这种叫“全功能的系统”。什么叫全功能系统?就是它可以直接使用浏览器、可以跑vibe coding、可以调用office,这些东西都是可以工作的。对于本地的算力其实并没有特别高的要求,所有的AI都是调云端的算力。它通过即时通讯工具来工作。我们想去跟clawdbot聊天的时候,你可以打开:

  • Telegram
  • Discord
  • iMessage
  • WhatsApp
  • 或者是给你发短信

都是可以的。国内的不行,像什么微信搞不定这事,因为微信对于这种机器人是封闭的,比较严格的,怕各种黑灰产。

很多人想去用iMessage,就是苹果系统的这种iMessage,这个就没办法,你必须使用Mac mini。你说我现在想整个Windows、想整个Linux上iMessage?上不去。这个iMessage也不是一个开放系统。很多苹果全家桶的玩家,特别是在程序员和AI玩家里头,苹果全家桶玩家的比例是很大的,肯定是喜欢上Mac mini的。家里头其他的闲置电脑其实也可以跑,Windows电脑也可以。但是如果你要在Windows电脑里跑,最好是装WSL。WSL就是Windows里面的Linux,现在Windows新的系统里边都是可以装一个Linux系统的。然后Linux电脑,这个肯定也是没问题的。我准备上NAS了,家里NAS已经跑了一大堆的各种各样的Docker了,它也是可以跑上去的。

云主机也没毛病。你都花了200刀去买套餐了,那你一个月花5刀去租个云主机跑这个clawdbot肯定也是没问题的。Oracle云上有免费的主机,大家可以上去玩耍一下。NAS、瘦服务器或者是在云主机上跑clawdbot,浏览器也是能用的,但是会比较费劲。vibe coding就要稍微克制一点了。如果是在你的Mac mini上,你就可以给它下指令,说打开哪个vibe coding的工具,然后在里边去给我写一什么产品出来,他自己吭哧吭哧就干活去了。你可以每天晚上睡觉之前给他布置一大堆任务,早上起来看看,完成几个、没有完成的部分你还可以去辅助一下。他是这样来干活的。你要是在云主机上,就不能干这活了。

Clawdbot是不是一次革命性的创新?

方向上肯定是。这个方向也很明确,就是无限记忆、私有部署、绝对隐私保护、7*24小时驻留、随时待命、主动沟通和提醒,基本可以解决各种问题。随着模型能力的提升、agent skills的发展,他的能力一定还会继续爆炸式增长。大模型厂商应该会争先恐后的推出新套餐了。因为有了前车之鉴,Anthropic估计过一段时间还是会封他的。这个咱们预言一下,咱们打个赌,猜一猜会不会把他封掉?前面open code用户量上去以后,Anthropic就直接把他封掉了。

因为现在买TOKEN基本上是两套玩法:一套就是你具体按100万TOKEN多少钱去算;另外一套就是给你套餐,这个编程套餐。因为现在编程实在是太烧TOKEN了,所以Anthropic出了这种编程套餐,OpenAI、谷歌都出了这种编程套餐。但是Anthropic还是希望,如果你想要去买它的编程套餐,你就只能用Claude code,你不能用其他的东西。像咱们现在讲这个clawdbot,这就不允许用。那么OpenAI跟谷歌应该会继续支持你。像open code这块,在Anthropic说我封闭它之后,OpenAI说我们准备继续支持。没毛病,你买我的plus套餐、Pro套餐,我都继续支持你。谷歌在这一块其实是放的比较宽松的,只要你愿意用,谷歌还是愿意笑脸相迎的。

国内的模型平台的话,也应该会推出一些专门的套餐,应该是会像code套餐这样,都是可以挂上使的。国内平台的code套餐基本上有5美金一个月的、10美金一个月的,甚至可能最便宜的有3美金一个月的。他们都是去仿真Anthropic的这个API形式,只要我仿真好了,就往上挂就完了,都是可以用的。

硬件与巨头的新机会

几个代表科技巨头的彩色球体悬浮在一张棋盘上方,象征市场博弈,羊皮纸,钢笔彩色手绘的统一风格。

家庭瘦服务器应该有新的应用场景,以后的NAS也可以配更好的CPU GPU了。至于家里是不是要买一台Mac mini,让我再犹豫几天吧,反正我目前为止还没有下决心再去买一个Mac mini。至于Mac mini农场,也许会在一段时间内流行起来。什么叫Mac mini农场?就是在一个房间里边装一大堆的Mac mini,允许大家从远程去访问它、替你去维护,我们只管去付租金就可以了。这可能也是一种未来的服务形式。

黑苹果可能会焕发第二春。什么叫黑苹果?就是在一些比较便宜的Intel这种架构上,使用macOS系统重新去破解,然后给你装上,让你去使用。这个东西叫黑苹果。其实黑苹果随着后来苹果出M系列芯片以后,已经不是那么活跃了,但是现在的话,应该会重新再活跃起来。

腾讯、Meta、苹果、谷歌机会来了,就看谁能抓得住了。为什么他们机会来了?他们做即时通讯工具的。既然大家觉得以后的这些个人助理应该是活在WhatsApp、活在Telegram、活在Discord里头了,腾讯说我这有微信,干脆我在这边给你配一个助理不就完事了吗?你有什么事跟助理说不就完了吗?我觉得他们未来是有机会的。至于说Meta的话,你像WhatsApp是它的,Facebook Messenger也是它的,全世界最大的两个即时通讯工具都是它的。苹果自己也是有iMessage的。它们都是有机会去腾飞一下的。

最后总结一下

又一个神奇的AI工具发布了,赶快玩起来吧。甭管好不好使,大家一定要去玩起来。7*24小时永久驻留、永久记忆、主动提醒、全能助理,这应该就是未来的方向了,这个基本上可以确定。助理已经这么强大了,具体做什么就是留给我们的问题了。网上的介绍视频里头经常是这样的,他问clawdbot:“你觉得我该干点什么?”还是要有自己的想法。只要烧得起TOKEN,我们每个人都会得到一个强大的全能助理。

好,这一期就讲到这里。感谢大家收听,请帮忙点赞、点小铃铛,参加DISCORD讨论群。也欢迎有兴趣、有能力的朋友加入我们的付费频道。再见。


背景图片

🔲 ☆

比C快5倍!FFmpeg 专门感谢腾讯发送的手写的 ARM 汇编补丁

比C快5倍!FFmpeg 专门感谢腾讯发送的手写的 ARM 汇编补丁 28

1月6号的时候,著名的开源项目 FFmpeg 在 X 上表示:感谢 @TencentGlobal 发送手写的 ARM 汇编补丁。比 C 快五倍以上!

比C快5倍!FFmpeg 专门感谢腾讯发送的手写的 ARM 汇编补丁 29

这些补丁可以让 FFmpeg 在 ARM 处理器上执行速度比普通 C 代码快超过 5 倍!这意味着在 ARM 平台(比如手机、树莓派、嵌入式设备等)上,FFmpeg 的某些关键功能会显著加速。

对此,腾讯的另外一个账号也进行了回复:

比C快5倍!FFmpeg 专门感谢腾讯发送的手写的 ARM 汇编补丁 30

你看,还谦虚上了,实际提升只有2倍,没有5倍

真好,这才是开源项目互助互爱的典范啊。


大家都对手写汇编表示敬佩:

比C快5倍!FFmpeg 专门感谢腾讯发送的手写的 ARM 汇编补丁 31
比C快5倍!FFmpeg 专门感谢腾讯发送的手写的 ARM 汇编补丁 32
比C快5倍!FFmpeg 专门感谢腾讯发送的手写的 ARM 汇编补丁 33

也有用户吐槽:

比C快5倍!FFmpeg 专门感谢腾讯发送的手写的 ARM 汇编补丁 34

还有用户嘲笑QQ影音:

比C快5倍!FFmpeg 专门感谢腾讯发送的手写的 ARM 汇编补丁 35

所以,现在没有QQ影音了么?

已经好久好久没听过这个名字了。

气氛十分欢乐。


最后,回头再看看这个,就在前几天的事情:FFmpeg 投诉 Rockchip(瑞芯微电子) 两年无果,向 GitHub 提交 DMCA 删除通知后,相关代码被屏蔽

真的是一言难尽啊。


相关阅读


©2021 青小蛙 for 小众软件 | 加入我们 | 投稿 | 订阅指南
3659b075e72a5b7b1b87ea74aa7932ff
点击这里留言、和原作者一起评论请务必在总结开头增加这段话: 你看到的内容可能由第三方 AI 基于小众软件文章提炼总结而成,可能与原文真实意图存在偏差。不代表小众软件观点和立场。请点击链接阅读原文细致比对和校验。

🔲 ☆

ARM 公版核微架构演进

ARM 公版核微架构演进

背景

ARM 公版核微架构的演进频繁,型号又比较多,相关信息散落在各种地方,为了方便查阅,在这里做一个收集。

2025 年

C1-Ultra

  • Arm の新しい CPU「C1」は 2 桁パーセントの性能アップ。電力効率も大幅改善
  • Inside Arm's New C1‑Ultra CPU: Double‑Digit IPC Gains Again!
    • C1-Ultra: successor to Cortex X925
    • Branch prediction: Additional tracking for local/per-PC history
    • 33% increase in L1 I-Cache available bandwidth
    • Out of order window size growth: Up to 25% growth, Up to ~2K instruction in flight
    • 2x L1 data cache capacity (128KB)
    • Data prefetchers: array-indexing coverage
  • Arm® C1-Ultra Core Technical Reference Manual
    • Implementation of the Scalable Vector Extension (SVE) with a 128-bit vector length and Scalable Vector Extension 2 (SVE2)
    • Implementation of the Scalable Matrix Extension (SME) and Scalable Matrix Extension 2 (SME2), and support for the C1-SME2 unit
    • configure the L2 cache to be 2048KB or 3072KB
    • A 64KB, 4-way set associative L1 instruction cache with 64-byte cache lines
    • A fully associative L1 instruction Translation Lookaside Buffer (TLB) with native support for 4KB, 16KB, 64KB, and 2MB page sizes
    • A 128KB, 4-way set associative cache with 64-byte cache lines
    • A fully associative L1 data TLB with native support for 4KB, 16KB, and 64KB page sizes and 2MB and 512MB block sizes
    • L2 cache is private to the core and can be configured to be 2MB 8-way set associative or 3MB 12-way set associative
    • L1 instruction TLB, Fully associative, 128 entries
    • L1 data TLB, Fully associative, 96 entries
    • L1 Statistical Profiling Extension (SPE) TLB, Located in the SPE block, VA to PA translations of any page and block size, 1 entry
    • L1 TRace Buffer Extension (TRBE) TLB, VA to PA translations of any page and block size, 1 entry
    • L2 TLB, Shared by instructions and data, 8-way set associative, 2048 entries
    • L1 instruction cache, 64KB, 4-way set associative, Virtually Indexed, Physically Tagged (VIPT) behaving as Physically Indexed, Physically Tagged (PIPT), Pseudo-Least Recently Used (LRU) cache replacement policy for L1, 32 bytes per cycle interface with L2
    • L1 data cache, 128KB, 4-way set associative, Virtually Indexed, Physically Tagged (VIPT) behaving as Physically Indexed, Physically Tagged (PIPT), Re-Reference Interval Prediction (RRIP) replacement policy, 4×64-bit read paths and 4×64-bit write paths for the integer execute pipeline, 4×128-bit read paths and 4×128-bit write paths for the vector execute pipeline
    • L2 cache, 2MB 8-way set associative with 4 banks or 3MB 12-way set associative with 4 banks, Physically Indexed, Physically Tagged (PIPT), Dynamic biased cache replacement policy, One CHI Issue E compliant interfaces with 256-bit read and write DAT channel widths

C1-Pro

  • Arm Lumex C1-Pro CPU Core: What You Need to Know
    • C1-Pro: successor to Cortex-A725
    • Larger direction predictor and branch history
    • 2x capacity 0-cycle BTB
    • 16x capacity 1-cycle BTB
    • 50% more L1 Instruction TLB capacity
    • Increase effective L1D cache bandwidth
    • Lower latency L2 TLB hit
    • New indirect prefetcher

2024 年

Cortex X925

  • Arm Unveils 2024 CPU Core Designs, Cortex X925, A725 and A520: Arm v9.2 Redefined For 3nm Archive
    • Decode & Dispatch: 10-wide
    • SIMD/FP execution: 6x 128b
    • Integer ALU pipelines: 1- and 2-cycle operations
    • Integer multiply execution: 4x versus Cortex-X4
    • FP compare execution: 2x versus Cortex-X4
    • >2x increase in SIMD/FP issue queues
    • 2x increase in max instruction-window capacity(注:Cortex X4 是 384x2,推测 Cortex X925 是 768x2)
    • Sign-extension instruction elimination
    • Branch prediction: 2x instruction window size
    • Instruction Fetch: 2x increase in L1 I$ available bandwidth, 2x increase in L1 iTLB size, Fold out unconditional direct branches
    • 3 -> 4 load pipelines
    • 2x increase in L1 D$ available bandwidth
    • 25-40% in back-end OoO growth
  • Arm® Cortex-X925 Core Technical Reference Manual
    • Implementation of the Scalable Vector Extension (SVE) with a 128-bit vector length and Scalable Vector Extension 2 (SVE2)
    • configure the L2 cache to be 2048KB or 3072KB
    • A 64KB, 4-way set associative L1 instruction cache with 64-byte cache lines
    • A fully associative L1 instruction Translation Lookaside Buffer (TLB) with native support for 4KB, 16KB, 64KB, and 2MB page sizes
    • A 64KB, 4-way set associative cache with 64-byte cache lines
    • A fully associative L1 data TLB with native support for 4KB, 16KB and 64KB page sizes and 2MB and 512MB block sizes
    • L2 cache is private to the core and can be configured to be 2MB 8-way set associative or 3MB 12-way set associative
    • L1 instruction TLB, Caches entries at the 4KB, 16KB, 64KB, or 2MB granularity of Virtual Address (VA) to Physical Address (PA) mapping only, Fully associative, 128 entries
    • L1 data TLB, Caches entries at the 4KB, 16KB, 64KB, 2MB, or 512MB granularity of VA to PA mappings only, Fully associative, 96 entries
    • L2 TLB, Shared by instructions and data, VA to PA mappings for 4KB, 16KB, 64KB, 2MB, 32MB, 512MB, and 1GB block sizes, Intermediate Physical Address (IPA) to PA mappings for: 2MB and 1GB block sizes in a 4KB translation granule, 32MB block size in a 16KB translation granule, 512MB block size in a 64KB granule; Intermediate PAs (descriptor PAs) obtained during a translation table walk, 8-way set associative, 2048 entries
    • L1 instruction cache, 64KB, 4-way set associative, Virtually Indexed, Physically Tagged (VIPT) behaving as Physically Indexed, Physically Tagged (PIPT)
    • The Cortex®-X925 core supports the AArch64 prefetch memory instructions, PRFM PLI, into the L1 instruction cache or L2 cache
    • L1 data cache, 64KB, 4-way set associative, Virtually Indexed, Physically Tagged (VIPT) behaving as Physically Indexed, Physically Tagged (PIPT), Re-Reference Interval Prediction (RRIP) replacement policy, 4×64-bit read paths and 4×64-bit write paths for the integer execute pipeline, 4×128-bit read paths and 4×128-bit write paths for the vector execute pipeline
    • L2 cache, 2MB 8-way set associative with 4 banks or 3MB 12-way set associative with 4 banks, Physically Indexed, Physically Tagged (PIPT)
  • Arm® Cortex-X925 Core Software Optimization Guide

2023 年

Cortex X4

Cortex A720

2022 年

Cortex X3

  • Arm Unveils Next-Gen Flagship Core: Cortex-X3
    • 50% larger L1 + L2 (new) BTB capacity
    • 10x larger L0 BTB capacity
    • New predictor dedicated for indirect branches
    • Double return-stack capacity (32 entries)
    • Mop cache 50% capacity (1.5K entries)
    • Removed 1 pipeline stage in Mop Cache fetch, 10->9 cycles for a branch mispredict
    • Increase decode bandwidth: 5->6
    • Integer ALUs increase 4->6: 2->4 single-cycle (SX), 2 single-/multi-cycle (MX)
    • ROB/MCQ: 288x2 -> 320x2
    • Integer load bandwdith: 24B -> 32B
    • Additional data prefetch engines: Spatial, Pointer/Indirect
  • Arm® Cortex‑X3 Core Technical Reference Manual

Neoverse V2

  • Arm Neoverse V2 platform: Leadership Performance and Power Efficiency for Next-Generation Cloud Computing, ML and HPC Workloads
    • 6-wide/8-wide front-end
    • 64KB ICache
    • 320+ OoO window
    • 8-wide dispatch
    • 8-wide retire
    • 2 LS + 1 LD / cycle
    • 64KB DCache
    • 6-ALU + 2-branch
    • Quad 128-bit low latency SIMD datapath
    • L2 10-cycle load-to-use, 128B/cycle, private L2 cache 1 or 2 MB
    • Two predicted branches per cycle
    • Predictor acts as ICache prefetcher
    • 64kB, 4-way set-associative L1 instruction cache
    • Two-level Branch Target Buffer
    • 8 table TAGE direction predictor with staged output
    • 10x larger nanoBTB
    • Split main BTB into two levels with 50% more entries
    • TAGE: 2x larger tables with 2-way associativity, Longer history
    • Indirect branches: Dedicated predictor
    • Fetch bandwidth: Doubled instruction TLB and cache BW
    • Fetch Queue: Doubled from 16 to 32 entries
    • Fill Buffer: Increased size from 12 to 16 entries
    • Decode bandwidth: Increased decoder lanes from 5 to 6, Increased Decode Queue from 16 to 24 entries
    • Rename checkpoints: Increased from 5 to 6 total checkpoints, Increased from 3 to 5 vector checkpoints
    • Late read of physical register file – no data in IQs
    • Result caches with lazy writeback
    • Added two more single-cycle ALUs
    • Larger Issue Queues, SX/MX: Increased from 20 to 22 entries, VX: Increased from 20 to 28 entries
    • Predicate operations: Doubled predicate bandwidth
    • Zero latency MOV; Subset of register-register and immediate move operations execute with zero latency
    • Instruction fusion: More fusion cases, including CMP + CSEL/CSET
    • Two load/store pipes + one load pipe
    • 4 x 8B result busses (integer)
    • 3 x 16B result busses (FP, SVE, Neon)
    • ST to LD forwarding at L1 hit latency
    • RST and MB to reduce tag and data accesses
    • Fully-associative L1 DTLB with multiple page sizes
    • 64kB 4-way set associative Dcache
    • TLB Increased from 40 to 48 entries
    • Replacement policy Changed from PLRU to dynamic RRIP
    • Larger Queues: Store Buffer, ReadAfterRead, ReadAfterWrite
    • Efficiency: VA hash based store to load forwarding
    • Multiple prefetching engines training on L1 and L2 accesses: Spatial Memory Streaming, Best Offset, Stride, Correlated Miss Cache, Page
    • New PF engines: Global SMS – larger offsets than SMS, Sampling Indirect Prefetch – pointer dereference, TableWalk – Page Table Entrie
    • Private unified Level 2 cache, 8-way SA, 4 independent banks
    • 64B read or write per 2 cycles per bank = 128B/cycle total
    • 96-entry Transaction Queue
    • Inclusive with L1 caches for efficient data and instruction coherency
    • AMBA CHI interface with 256b DAT channels
    • Capacity 2MB/8-way with latency of 1MB (10-cycle ld-to-use)
    • Replacement policy 6-state RRIP (up from 4)
  • Hot Chips 2023: Arm’s Neoverse V2
  • Arm® Neoverse™ V2 Core Technical Reference Manual
  • Arm Neoverse V2 Software Optimization Guide

2021 年

Cortex X2

2020 年

Neoverse N2

  • Arm Neoverse N2: Arm’s 2nd generation high performance infrastructure CPUs and system IPs
    • Branch Prediction, 2x 8 instrs (up to 2 taken per cycle), 2x improvement
    • Nano BTB (0 cyc taken-branch bubble), 64 entry, 4x improvement
    • Conditional branch direction state, 1.5x improvement
    • Main BTB, 8K entry, 1.33x improvement
    • Alt-Path Branch Prediction
    • 64KB Instruction cache
    • 1.5K entry Mop Cache
    • 16-entry Fetch Queue, 1.33x improvement
    • Fetch Width: 4 instr from i$, 5 instr from MOP$, Up to 1.5x improvement
    • Early branch redirect: uncond + cond
    • Decode width: 4 (I-cache) or 5 (Mop cache), Up to 1.25x improvement
    • Branch predict up to 16-inst/cycle, 2-taken/cycle
    • New Macro-op (MOP) cache with 1.5k entries
    • 50% larger branch direction predicton
    • 33% larger BTB with shorter average latency
    • Early re-steering for conditional branches that miss the BTB
    • Rename width: 5 instrs, 1.2x improvement
    • Rename Checkpointing: Yes
    • ROB size: 160+, 1.25x improvement
    • ALUs: 4, 1.33x improvement
    • Branch resolution: 2 per cycle, 2x improvement
    • Overall Pipeline Depth: 10 cycles, 1.1x improvement
    • 64KB L1 Data cache
    • Private 512KB/1MB L2 Cache
    • AGU: 2-LD/ST + 1 LD, 1.5x improvement
    • L1 LD Hit bandwidth: 3x 16B/cycle, 1.5x improvement
    • Store data B/W: 32B/cycle, 2x improvement
    • L2 bandwidth: 64B read + 64B write, 2x improvement
    • L2 transactions: 64, 1.3x improvement
    • Data Prefetch Engines: Stride, spatial/region, stream, temporal
    • Correlated Miss Caching (CMC) prefetching

Neoverse V1

  • SW defined cars: HPC, from the cloud to the dashboard for an amazing driver experience
    • Faster run-ahead for prefetching into the I$ (2x32B bandwidth)
    • 33% larger BTBs (8K entry)
    • 6x nano BTB (96 entry), zero-cycle bubble
    • 2x number of concurrent code regions tracked in front-end
    • Introduction of Mop Cache, L0 decoded instruction cache (3K entry)
    • high dispatch bandwidth, 8-instrs per cycle, 2x increase, I$ decode bandwidth increased from 4x to 5x
    • Lower latency decode pipeline by 1 stage
    • OoO window size, 2x+ ROB (256 entry + compression)
    • Increase superscalar integer execution bandwidth, 1->2 Branch Execution, 3->4 ALU
    • 2x vector/fp bandwidth, 2x256b – SVE (new), 4x128b – Neon/FP
    • 3rd LD AGU/pipe (50% incr), LS LS LD
    • LD/ST data bandwidth, LD: 2x16B -> 3x16B, LD (SVE): 2x32B, ST: 16B -> 32B (2x), broken out into separate issue pipes
    • Number of outstanding external memory transactions (48->96)
    • MMU capacity 1.2K->2K entry (67% incr)
    • L2 latency reduced by 1 cycle for 1M (now 10cyc load to use)
    • 11+ stage accordion pipeline
    • 8-wide front-end / 15-wide issue
    • Four 64-bit integer ALUs + two dedicated Branch units
    • 2x 256-bit SVE datapaths
    • 4x 128-bit Neon/FP datapaths
    • 3x load / store addr
    • 3x load data & 2x store data pipeline
    • 8-wide Instruction fetch
    • 5-8 wide decode / rename
    • pipeline: P1 P2 F1 F2 DE1 RR RD I0 I1 I2 ...

Cortex X1

Cortex A78

2019 年

Neoverse N1

  • The Arm Neoverse N1 Platform: Building Blocks for the Next-Gen Cloud-to-Edge Infrastructure SoC
    • 4-wide front-end
    • dispatching/committing up to 8 instructions per cycle
    • three ALUs, a branch execution unit, two Advanced SIMD units, and two load/store execution units
    • minimum misprediction penalty is 11-cycle
    • fetch up to 4 instructions per cycle
    • large 6K-entry main branch target buffer with 3-cycle access latency
    • 64-entry micro-BTB and a 16-entry nano-BTB
    • 12-entry fetch queue
    • fully associative 48-entry instruction TLB
    • 4-way set-associative 64KB I-cache
    • I-cache can deliver up to 16B of instructions per cycle
    • up to 8 outstanding I-cache refill requests
    • 4-wide decoder
    • renaming unit can receive up to 4 macro-ops per cycle
    • up to 8 micro-operations can be dispatched into the out-of-order engine each cycle
    • The commit queue can track up to 128 micro operations
    • up to 8 micro-ops can be committed per cycle
    • a distributed issue queue with more than 100 micro-operations
    • 4 integer execution pipelines, 2 load/store pipelines, and 2 Advanced SIMD pipelines
    • 64kB 4-way set associative L1 data cache, 4-cycle load to use latency and a bandwidth of 32 bytes/cycle
    • The core-private 8-way set associative L2 cache is up to 1MB in size and has a load-to-use latency of 11 cycles
    • can also be configured with smaller L2 cache sizes of 256kB and 512kB with a load-to-use latency of 9 cycles
    • L2 cache connects to the system via an AMBA 5 CHI interface with 16-byte data channels
    • L3 cluster cache can be up to 2MB, with a load-to-use latency ranging between 28 and 33 cycles
    • up to 256MB of shared system-level cache

🔲 ☆

ARM 公版核微架构演进

ARM 公版核微架构演进

背景

ARM 公版核微架构的演进频繁,型号又比较多,相关信息散落在各种地方,为了方便查阅,在这里做一个收集。

2025 年

C1

  • Inside Arm's New C1‑Ultra CPU: Double‑Digit IPC Gains Again!
    • C1-Ultra: successor to Cortex X925
    • Branch prediction: Additional tracking for local/per-PC history
    • 33% increase in L1 I-Cache available bandwidth
    • Out of order window size growth: Up to 25% growth, Up to ~2K instruction in flight
    • 2x L1 data cache capacity (128KB)
    • Data prefetchers: array-indexing coverage
  • Arm Lumex C1-Pro CPU Core: What You Need to Know
    • C1-Pro: successor to Cortex-A725
    • Larger direction predictor and branch history
    • 2x capacity 0-cycle BTB
    • 16x capacity 1-cycle BTB
    • 50% more L1 Instruction TLB capacity
    • Increase effective L1D cache bandwidth
    • Lower latency L2 TLB hit
    • New indirect prefetcher

2024 年

Cortex X925

2023 年

Cortex X4

2022 年

Cortex X3

  • Arm Unveils Next-Gen Flagship Core: Cortex-X3
    • 50% larger L1 + L2 (new) BTB capacity
    • 10x larger L0 BTB capacity
    • New predictor dedicated for indirect branches
    • Double return-stack capacity (32 entries)
    • Mop cache 50% capacity (1.5K entries)
    • Removed 1 pipeline stage in Mop Cache fetch, 10->9 cycles for a branch mispredict
    • Increase decode bandwidth: 5->6
    • Integer ALUs increase 4->6: 2->4 single-cycle (SX), 2 single-/multi-cycle (MX)
    • ROB/MCQ: 288x2 -> 320x2
    • Integer load bandwdith: 24B -> 32B
    • Additional data prefetch engines: Spatial, Pointer/Indirect
  • Arm® Cortex‑X3 Core Technical Reference Manual

Neoverse V2

  • Arm Neoverse V2 platform: Leadership Performance and Power Efficiency for Next-Generation Cloud Computing, ML and HPC Workloads
    • 6-wide/8-wide front-end
    • 64KB ICache
    • 320+ OoO window
    • 8-wide dispatch
    • 8-wide retire
    • 2 LS + 1 LD / cycle
    • 64KB DCache
    • 6-ALU + 2-branch
    • Quad 128-bit low latency SIMD datapath
    • L2 10-cycle load-to-use, 128B/cycle, private L2 cache 1 or 2 MB
    • Two predicted branches per cycle
    • Predictor acts as ICache prefetcher
    • 64kB, 4-way set-associative L1 instruction cache
    • Two-level Branch Target Buffer
    • 8 table TAGE direction predictor with staged output
    • 10x larger nanoBTB
    • Split main BTB into two levels with 50% more entries
    • TAGE: 2x larger tables with 2-way associativity, Longer history
    • Indirect branches: Dedicated predictor
    • Fetch bandwidth: Doubled instruction TLB and cache BW
    • Fetch Queue: Doubled from 16 to 32 entries
    • Fill Buffer: Increased size from 12 to 16 entries
    • Decode bandwidth: Increased decoder lanes from 5 to 6, Increased Decode Queue from 16 to 24 entries
    • Rename checkpoints: Increased from 5 to 6 total checkpoints, Increased from 3 to 5 vector checkpoints
    • Late read of physical register file – no data in IQs
    • Result caches with lazy writeback
    • Added two more single-cycle ALUs
    • Larger Issue Queues, SX/MX: Increased from 20 to 22 entries, VX: Increased from 20 to 28 entries
    • Predicate operations: Doubled predicate bandwidth
    • Zero latency MOV; Subset of register-register and immediate move operations execute with zero latency
    • Instruction fusion: More fusion cases, including CMP + CSEL/CSET
    • Two load/store pipes + one load pipe
    • 4 x 8B result busses (integer)
    • 3 x 16B result busses (FP, SVE, Neon)
    • ST to LD forwarding at L1 hit latency
    • RST and MB to reduce tag and data accesses
    • Fully-associative L1 DTLB with multiple page sizes
    • 64kB 4-way set associative Dcache
    • TLB Increased from 40 to 48 entries
    • Replacement policy Changed from PLRU to dynamic RRIP
    • Larger Queues: Store Buffer, ReadAfterRead, ReadAfterWrite
    • Efficiency: VA hash based store to load forwarding
    • Multiple prefetching engines training on L1 and L2 accesses: Spatial Memory Streaming, Best Offset, Stride, Correlated Miss Cache, Page
    • New PF engines: Global SMS – larger offsets than SMS, Sampling Indirect Prefetch – pointer dereference, TableWalk – Page Table Entrie
    • Private unified Level 2 cache, 8-way SA, 4 independent banks
    • 64B read or write per 2 cycles per bank = 128B/cycle total
    • 96-entry Transaction Queue
    • Inclusive with L1 caches for efficient data and instruction coherency
    • AMBA CHI interface with 256b DAT channels
    • Capacity 2MB/8-way with latency of 1MB (10-cycle ld-to-use)
    • Replacement policy 6-state RRIP (up from 4)
  • Hot Chips 2023: Arm’s Neoverse V2
  • Arm® Neoverse™ V2 Core Technical Reference Manual
  • Arm Neoverse V2 Software Optimization Guide

2021 年

Cortex X2

2020 年

Neoverse N2

  • Arm Neoverse N2: Arm’s 2nd generation high performance infrastructure CPUs and system IPs
    • Branch Prediction, 2x 8 instrs (up to 2 taken per cycle), 2x improvement
    • Nano BTB (0 cyc taken-branch bubble), 64 entry, 4x improvement
    • Conditional branch direction state, 1.5x improvement
    • Main BTB, 8K entry, 1.33x improvement
    • Alt-Path Branch Prediction
    • 64KB Instruction cache
    • 1.5K entry Mop Cache
    • 16-entry Fetch Queue, 1.33x improvement
    • Fetch Width: 4 instr from i\(, 5 instr from MOP\), Up to 1.5x improvement
    • Early branch redirect: uncond + cond
    • Decode width: 4 (I-cache) or 5 (Mop cache), Up to 1.25x improvement
    • Branch predict up to 16-inst/cycle, 2-taken/cycle
    • New Macro-op (MOP) cache with 1.5k entries
    • 50% larger branch direction predicton
    • 33% larger BTB with shorter average latency
    • Early re-steering for conditional branches that miss the BTB
    • Rename width: 5 instrs, 1.2x improvement
    • Rename Checkpointing: Yes
    • ROB size: 160+, 1.25x improvement
    • ALUs: 4, 1.33x improvement
    • Branch resolution: 2 per cycle, 2x improvement
    • Overall Pipeline Depth: 10 cycles, 1.1x improvement
    • 64KB L1 Data cache
    • Private 512KB/1MB L2 Cache
    • AGU: 2-LD/ST + 1 LD, 1.5x improvement
    • L1 LD Hit bandwidth: 3x 16B/cycle, 1.5x improvement
    • Store data B/W: 32B/cycle, 2x improvement
    • L2 bandwidth: 64B read + 64B write, 2x improvement
    • L2 transactions: 64, 1.3x improvement
    • Data Prefetch Engines: Stride, spatial/region, stream, temporal
    • Correlated Miss Caching (CMC) prefetching

Neoverse V1

  • SW defined cars: HPC, from the cloud to the dashboard for an amazing driver experience
    • Faster run-ahead for prefetching into the I$ (2x32B bandwidth)
    • 33% larger BTBs (8K entry)
    • 6x nano BTB (96 entry), zero-cycle bubble
    • 2x number of concurrent code regions tracked in front-end
    • Introduction of Mop Cache, L0 decoded instruction cache (3K entry)
    • high dispatch bandwidth, 8-instrs per cycle, 2x increase, I$ decode bandwidth increased from 4x to 5x
    • Lower latency decode pipeline by 1 stage
    • OoO window size, 2x+ ROB (256 entry + compression)
    • Increase superscalar integer execution bandwidth, 1->2 Branch Execution, 3->4 ALU
    • 2x vector/fp bandwidth, 2x256b – SVE (new), 4x128b – Neon/FP
    • 3rd LD AGU/pipe (50% incr), LS LS LD
    • LD/ST data bandwidth, LD: 2x16B -> 3x16B, LD (SVE): 2x32B, ST: 16B -> 32B (2x), broken out into separate issue pipes
    • Number of outstanding external memory transactions (48->96)
    • MMU capacity 1.2K->2K entry (67% incr)
    • L2 latency reduced by 1 cycle for 1M (now 10cyc load to use)
    • 11+ stage accordion pipeline
    • 8-wide front-end / 15-wide issue
    • Four 64-bit integer ALUs + two dedicated Branch units
    • 2x 256-bit SVE datapaths
    • 4x 128-bit Neon/FP datapaths
    • 3x load / store addr
    • 3x load data & 2x store data pipeline
    • 8-wide Instruction fetch
    • 5-8 wide decode / rename
    • pipeline: P1 P2 F1 F2 DE1 RR RD I0 I1 I2 ...

Cortex X1

2019 年

Neoverse N1

  • The Arm Neoverse N1 Platform: Building Blocks for the Next-Gen Cloud-to-Edge Infrastructure SoC
    • 4-wide front-end
    • dispatching/committing up to 8 instructions per cycle
    • three ALUs, a branch execution unit, two Advanced SIMD units, and two load/store execution units
    • minimum misprediction penalty is 11-cycle
    • fetch up to 4 instructions per cycle
    • large 6K-entry main branch target buffer with 3-cycle access latency
    • 64-entry micro-BTB and a 16-entry nano-BTB
    • 12-entry fetch queue
    • fully associative 48-entry instruction TLB
    • 4-way set-associative 64KB I-cache
    • I-cache can deliver up to 16B of instructions per cycle
    • up to 8 outstanding I-cache refill requests
    • 4-wide decoder
    • renaming unit can receive up to 4 macro-ops per cycle
    • up to 8 micro-operations can be dispatched into the out-of-order engine each cycle
    • The commit queue can track up to 128 micro operations
    • up to 8 micro-ops can be committed per cycle
    • a distributed issue queue with more than 100 micro-operations
    • 4 integer execution pipelines, 2 load/store pipelines, and 2 Advanced SIMD pipelines
    • 64kB 4-way set associative L1 data cache, 4-cycle load to use latency and a bandwidth of 32 bytes/cycle
    • The core-private 8-way set associative L2 cache is up to 1MB in size and has a load-to-use latency of 11 cycles
    • can also be configured with smaller L2 cache sizes of 256kB and 512kB with a load-to-use latency of 9 cycles
    • L2 cache connects to the system via an AMBA 5 CHI interface with 16-byte data channels
    • L3 cluster cache can be up to 2MB, with a load-to-use latency ranging between 28 and 33 cycles
    • up to 256MB of shared system-level cache

🔲 ☆

PyCharm 2025.2.1 官方正版

PyCharm是一种Python IDE,带有一整套可以帮助用户在使用Python语言开发时提高其效率的工具,比如调试、语法高亮、Project管理、代码跳转、智能提示、自动完成、单元测试、版本控制。此外,该IDE提供了一些高级功能,以用于支持 …
🔲 ☆

ARM Neoverse V1 (代号 Zeus) 的 BTB 结构分析

ARM Neoverse V1 (代号 Zeus) 的 BTB 结构分析

背景

ARM Neoverse V1 是 ARM Neoverse N1 的下一代服务器 CPU,在 2020 年发布。此前我们分析过 Neoverse N1 的 BTB 设计。而 ARM Neoverse V1 在很多地方都和 Cortex-X1 类似,相比 Neoverse N1/Cortex-A76 有了一些改进,在这里对它的 BTB 做一些分析。

官方信息

首先收集了一些 ARM Neoverse V1 的 BTB 结构的官方信息:

简单整理一下官方信息,大概有两级 BTB:

  • 96-entry nano BTB, 1 cycle latency (0 cycle bubble)
  • 8K-entry main BTB
  • 2 predicted branches per cycle

但是很多细节是缺失的,因此下面结合微架构测试,进一步研究它的内部结构。

微架构测试

在之前的博客里,我们已经测试了各种处理器的 BTB,在这里也是一样的:按照一定的 stride 分布无条件(uncond)或总是跳转的有条件(cond)直接分支,构成一个链条,然后测量 CPI。在先前的 Neoverse N1 测试 里,我们只测试了无条件分支,但实际上,在 Neoverse N1 上用条件分支测出来的结果也是一样的,但在 Neoverse V1 上就不同了,所以在这里要分开讨论。

stride=4B uncond

首先是 stride=4B uncond 的情况:

可以看到,图像上出现了如下比较显著的台阶:

  • 第一个台阶到接近 64 条分支,CPI=1,对应了 96-entry 的 nano BTB,但是没有体现出完整的 96 的容量
  • 第二个台阶到 16384 条分支,CPI 在 5 到 6 之间,大于 main BTB 的 2 cycle latency,说明此时没有命中 main BTB,而是要等到取指和译码后,计算出正确的目的地址再回滚,导致了 5+ cycle latency;16384 对应 64KB L1 ICache 容量

那么 stride=4B uncond 的情况下就遗留了如下问题:

  1. nano BTB 没表现出 96 的容量,只表现出接近 64 的容量
  2. 没有观察到 2 predicted branches per cycle
  3. 没有命中 main BTB

stride=4B cond

stride=4B cond 的情况:

可以看到,图像上出现了如下比较显著的台阶:

  • 第一个台阶到 48 条分支,CPI=1,对应了 96-entry 的 nano BTB,但是没有体现出完整的 96 的容量
  • 之后没有明显的分界点,性能波动剧烈,没有观察到 main BTB 的台阶

nano BTB 只表现出 48 的容量,刚好是 96 的一半;同时没有观察到 2 predicted branches per cycle。考虑这两点,可以认为 nano BTB 的组织方式和分支类型有关,当分支过于密集(stride=4B)或者用条件分支(cond)时,不能得到完整的 96-entry 的大小,此时也会回落到 CPI=1 的情况。

那么 stride=4B cond 的情况下就遗留了如下问题:

  1. 没有命中 main BTB

stride=8B uncond

stride=8B uncond 的情况:

可以看到,图像上出现了如下比较显著的台阶:

  • 第一个台阶到 96 条分支,CPI=0.5,对应了 96-entry 的 nano BTB,体现了 2 predicted branches per cycle
  • 第二个台阶到 8192 条分支,CPI=1,对应 main BTB,此时也对应了 64KB L1 ICache;此外,从 4096 开始有略微的上升

此时 nano BTB 完整地表现出了它的 96-entry 容量,并且实现了 CPI=0.5 的效果。main BTB 也实现了 CPI=1,考虑到它的容量不太可能单周期给出一个分支的结果,大概率是两个周期预测两条分支指令。

那么 stride=8B uncond 的情况下就遗留了如下问题:

  1. 从 4096 条分支开始性能有略微的下降

stride=8B cond

stride=8B cond 的情况:

可以看到,图像上出现了如下比较显著的台阶:

  • 第一个台阶到 48 条分支,CPI=1,对应了 96-entry 的 nano BTB,没有 2 predicted branches per cycle,容量也只有 96 的一半
  • 第二个台阶到 8192 条分支,CPI=2,对应 main BTB,此时也对应了 64KB L1 ICache

和之前一样,遇到 cond 分支,nano BTB 的容量只有一半,也观察不到 2 predicted branches per cycle。另一边,main BTB 的 CPI 也到了 2,意味着此时 main BTB 也只能两个周期预测一条分支指令,和之前的分析吻合。

那么为什么用条件分支,就不能预测两条分支指令了呢?猜测是,BTB 可以一次给出两条分支的信息,但是没有时间去同时预测这两条分支的方向。所以就回落到了普通的 2 cycle BTB 情况。

stride=16B uncond

stride=16B uncond 的情况:

可以看到,图像上出现了如下比较显著的台阶:

  • 第一个台阶到 96 条分支,CPI=0.5,对应了 96-entry 的 nano BTB,体现了 2 predicted branches per cycle
  • 第二个台阶到 2048 条分支,CPI=1;略微上升到 4096,此时是 64KB L1 ICache 的容量;到 8192 出现明显突变,对应 main BTB 容量

那么 stride=16B uncond 的情况下就遗留了如下问题:

  1. 从 2048 条分支开始性能有略微的下降

stride=16B cond

stride=16B cond 的情况:

可以看到,图像上出现了如下比较显著的台阶:

  • 第一个台阶到 48 条分支,CPI=1,对应了 96-entry 的 nano BTB,没有 2 predicted branches per cycle,容量也只有 96 的一半
  • 第二个台阶到 8192 条分支,CPI=2,对应 main BTB

预测的效果和 stride=8B cond 完全相同。

那么 stride=16B cond 的情况下就遗留了如下问题:

  1. 64KB ICache 应该在 4096 条分支导致瓶颈,但是实际没有观察到

stride=32B uncond

stride=32B uncond 的情况:

可以看到,图像上出现了如下比较显著的台阶:

  • 第一个台阶到 96 条分支,CPI=0.5,对应了 96-entry 的 nano BTB,体现了 2 predicted branches per cycle
  • 第二个台阶到 1024 条分支,CPI=1;略微上升到 2048,此时是 64KB L1 ICache 的容量;到 8192 右侧出现明显突变

那么 stride=32B uncond 的情况下就遗留了如下问题:

  1. 从 1024 条分支开始性能有略微的下降
  2. 性能明显下降的点在 8192 右侧,而不是 8192

stride=32B cond

stride=32B cond 的情况:

可以看到,图像上出现了如下比较显著的台阶:

  • 第一个台阶到 48 条分支,CPI=1,对应了 96-entry 的 nano BTB,没有 2 predicted branches per cycle,容量也只有 96 的一半
  • 第二个台阶到 2048 条分支,CPI=2,对应 64KB L1 ICache 容量,之后缓慢上升,到 8192 出现性能突变,对应 main BTB 容量

基本符合预期,只是在 stride=16B cond 的基础上,引入了 64KB L1 ICache 导致的性能下降。

stride=64B uncond

stride=64B uncond 的情况:

可以看到,图像上出现了如下比较显著的台阶:

  • 第一个台阶到 96 条分支,CPI=0.5,对应了 96-entry 的 nano BTB,体现了 2 predicted branches per cycle
  • 第二个台阶到 1024 条分支,CPI=1,对应 64KB L1 ICache 的容量
  • 第三个台阶到 4096,CPI=3,对应 main BTB 的容量;main BTB 容量减半,意味着 main BTB 应当是个组相连结构

stride=64B cond

stride=64B cond 的情况:

可以看到,图像上出现了如下比较显著的台阶:

  • 第一个台阶到 48 条分支,CPI=1,对应了 96-entry 的 nano BTB,没有 2 predicted branches per cycle,容量也只有 96 的一半
  • 第二个台阶到 1024 条分支,CPI=2,对应 64KB L1 ICache 容量,之后缓慢上升,到 4096 出现性能突变,对应 main BTB 容量;main BTB 容量只有 8192 的一半,意味着它是组相连结构

小结

测试到这里就差不多了,更大的 stride 得到的也是类似的结果,总结一下前面的发现:

  • nano BTB 是 96-entry,1 cycle latency,对于 uncond 分支可以做到一次预测两条分支,大小不随着 stride 变化,对应全相连结构
  • main BTB 是 8K-entry,2 cycle latency,对于 uncond 分支可以做到一次预测两条分支,此时可以达到 CPI=1;容量随着 stride 变化,对应组相连结构
  • 64KB ICache 很多时候会比 main BTB 更早成为瓶颈

也总结一下前面发现了各种没有解释的遗留问题:

  • cond 分支情况下,没有 2 predicted branches per cycle,此时两级 BTB 分别可以做到 CPI=1 和 CPI=2,同时 nano BTB 容量减半到 48:解释见后
  • stride=4B uncond/cond 的情况下,main BTB 没有像预期那样工作:解释见后
  • stride=8B/16B/32B uncond 的情况下,4096/2048/1024 条分支处出现了性能下降:暂无解释
  • stride=32B uncond 的情况下,main BTB 导致的拐点应该在 8192,但实际上在 8192 右侧:暂无解释
  • stride=16B cond 的情况下,64KB ICache 应该在 4096 条分支导致瓶颈,但是实际没有观察到:暂无解释

接下来尝试解析一下这些遗留问题背后的原理。部分遗留问题,并没有被解释出来,欢迎读者提出猜想。

解析遗留问题

cond 分支情况下,没有 2 predicted branches per cycle,同时 nano BTB 只有 48 的容量

比较相同 stride 下,cond 和 uncond 的情况,可以看到,cond 情况下两级 BTB 的 CPI 都翻倍,这意味着,当遇到全是 cond 分支时,大概是因为条件分支预测器的带宽问题,不能一次性预测两个 cond 分支的方向,而只能预测第一条 cond 分支,那么第二条 cond 分支的信息,即使通过 BTB 读取出来,也不能立即使用,还得等下一次的预测。

为了验证这个猜想,额外做了一组实验:把分支按照 cond, uncond, cond, uncond, ... 的顺序排列,也就是每个 cond 分支的目的地址有一条 uncond 分支。此时测出来的结果,和 uncond 相同,也就是可以做到 2 predicted branches per cycle。此时,BTB 依然一次提供了两条分支的信息,只不过条件分支预测器只预测了第一个 cond 的方向。如果它预测为不跳转,那么下一个 PC 就是 cond 分支的下一条指令;如果它预测为跳转,那么下一个 PC 就是 uncond 分支的目的地址。

nano BTB 的容量减半,意味着 nano BTB 的 96 的容量,实际上是 48 个 entry,每个 entry 最多记录两条分支。考虑到 nano BTB 的容量不随 stride 变化,大概率是全相连,并且是根据第一条分支的地址进行全相连匹配,这样,在 cond + cond 这种情况下,就只能表现出 48 的容量。但是 stride=4B uncond 的情况下表现出介于 48 和 64 之间的容量,还不知道是什么原因。

main BTB 的容量不变,意味着它在 cond + cond 的情况下,会退化为普通的 BTB,此时所有容量都可以用来保存 cond 分支,并且都能匹配到。

那么,具体是怎么做到 2 predicted branches per cycle 呢?猜测在执行的时候,检测这种一个分支的目的地址后,跟着一条 uncond 分支的情况:如果有的话,就把第二条分支的信息,放在第一条分支的信息后面(这在 Branch Target Buffer Organizations 中被称为 MB-BTB 结构),单个周期直接从 SRAM 读取出来,然后组成两个 fetch bundle:

  • prediction pc -- first branch pc
  • first branch target -- second branch pc

然后下一个周期从 second branch target 开始继续预测。根据官方信息,Neoverse V1 的 L1 ICache 支持 2x32B 的带宽,这个 2x 代表了可以从两个不同的地方读取指令,也就是 L1 ICache 至少是双 bank 甚至双端口的 SRAM。考虑到前面的测试中,CPI=0.5 的范围跨越了各种 stride,认为 L1 ICache 是双 bank 的可能写比较小,不然应该会观测到 bank conflict,大概率就是双端口了。

此外,考虑到 fetch bundle 的长度限制,first branch target 到 second branch pc 不能太远。在上面的测试中,这个距离总是 0;读者如果感兴趣,可以尝试把距离拉长,看看超过 32B 以后,是不是会让 2 predicted branches per cycle 失效。类似的表述,在 AMD Zen 4 Software Optimization Guide 中也有出现:

The branch target buffer (BTB) is a two-level structure accessed using the fetch address of the previous fetch block. Each BTB entry includes information for branches and their targets. Each BTB entry can hold up to two branches, and two pair cases are supported: • A conditional branch followed by another branch with both branches having their last byte in the same 64 byte aligned cacheline. • A direct branch (excluding CALLs) followed by a branch ending within the 64 byte aligned cacheline containing the target of the first branch. Predicting with BTB pairs allows two fetches to be predicted in one prediction cycle. 

上面的第二种情况,对应了第二条分支的 pc,在第一条分支的 target 的同一个 64 字节 cacheline 内的要求。可见,ARM 和 AMD 在 BTB 的设计上是趋同的。

小结,Neoverse V1 在满足如下条件时,可以做到 2 predicted branches per cycle:

  • uncond + uncond
  • cond + uncond

stride=4B uncond/cond 的情况下,main BTB 没有像预期那样工作

类似的情况,我们在分析 Neoverse N1 的时候就遇到了。Neoverse N1 的情况是,每对齐的 32B 块内,由于 6 路组相连,最多记录 6 条分支,而 stride=4B 时,有 8 条分支,所以出现了性能问题。

那么 Neoverse V1 是不是还是类似的情况呢?查阅 Neoverse V1 TRM,可以看到它的 L1 (main) BTB 的描述是:

  • Index: [15:4]
  • Data: [91:0]

回想之前 Neoverse N1 的 main BTB 容量:Index 是 [14:5],意味着有 1024 个 set;3 个 Way,每个 Way 里面是 82 bit 的数据,每个分支占用 41 bit,所以一共可以存 1024*3*2=6K 条分支。

类比一下,Neoverse V1 的 main BTB 容量也就可以计算得出:Index 是 [15:4],意味着有 4096 个 set;没有 Way,说明就是直接映射;92 bit 的数据,大概率也是每个分支占用一半也就是 46 bit,所以一共可以存 4096*2=8K 条分支,和官方数据吻合。在需要 2 predicted branches 的时候,就把这两个分支放到同一个 92-bit entry 内即可。一共占用 4096*92=376832 bit 也就是 46 KB 的空间。

那么,在 stride=4B 的情况下,对齐的 16B 块内的分支会被放到同一个 set 内,而每个 set 只能放两条分支,而 stride=4B 时需要放四条分支,这就导致了 main BTB 出现性能问题。

但比较奇怪的是,main BTB 的容量,在 stride=32B 时是 8192,而 stride=64B 时是 4096,这和 Index 是 PC[15:4] 不符,这成为了新的遗留问题。有一种可能,就是 TRM 写的不准确,Index 并非 PC[15:4]。另外还有一个佐证:Neoverse N2 的 BTB 设计和 Neoverse V1 基本相同,但是它的 TRM 写的 Index 就是 [11:0],这就肯定不是 PC[11:0] 了。

抛开 TRM,根据 JamesAslan 在 偷懒的 BTB?ARM Cortex X1 初探 中的测试,Main BTB 是四路组相连。如果按照四路组相连来考虑,那么 8K 条分支,实际上应该是 2048 个 set,2 个 way,一共是 4K 个 entry,每个 entry 最多保存两条分支。此时 Index 应该有 11 个 bit。在 2 way 每 way 两条分支等效为 4 way 的情况下,stride=4B 出现分支数比 way 数量更多的情况,stride=8B 则不会,意味着参与到 Index 的最低的 PC 应该是 PC[5],即每个对齐的 32B 块内,最多放四条分支(Neoverse N1 上是每个对齐的 32B 块内最多放六条分支)。这样的话,Index 可能实际上是 PC[15:5]。

总结

最后总结一下 Neoverse V1 的 BTB:

  • 48-entry(96 branches) nano BTB, at most 2 branches per entry, 1 cycle latency, at most 2 predicted branches every 1 cycle, fully associative
  • 4K-entry(8K branches) main BTB, at most 2 branches per entry, 2 cycle latency, at most 2 predicted branches every 2 cycles, 2-way(4-branch-way) set-associative, index PC[15:5]

当 uncond + uncond 或者 cond + uncond 时,可以实现每次预测两条分支;对于 cond + cond,每次只能预测一条分支。

2 predicted branches per cycle 通常也被称为 2 taken branches per cycle,简称 2 taken。

附录

Neoverse N2(代号 Perseus)的 BTB 结构分析

根据官方信息,Neoverse N2 和 Neoverse V1 的 BTB 配置十分类似,从数字来看只有 nano BTB 缩小到了 32-entry(64 branches),其余是相同的,例如 main BTB 容量也是 8K branches。实测下来,BTB 测试图像和 Neoverse V1 基本一样,只有 nano BTB 容量的区别。因此本文也可以认为是对 Neoverse N2 的 BTB 结构分析。考虑到 Neoverse N2 和 Neoverse V1 的发布时间相同,可以认为它们用的就是相同的前端设计,只是改了一下参数。

至于为啥 Neoverse N2 比 Neoverse V1 出的更晚但 nano BTB 还更小,可能是因为它是 N 系列而不是 V 系列,基于 Cortex-A710,所以容量上做了一些取舍。

各代 Neoverse 处理器的 BTB 结构对比

比较一下 Neoverse V1 和 Neoverse N1 的设计:

  • Neoverse N1 设计了三级 BTB(16+64+6K),分别对应 1-3 的周期的延迟,特别地,main BTB 设计了 fastpath 来实现一定情况下的 2 周期延迟
  • Neoverse V1 设计了两级 BTB(96+8K),分别对应 1-2 的周期的延迟,并且都支持 2 taken

Neoverse V1 相比 Neoverse N1,在容量和延迟上都有比较明显的提升,还额外给两级 BTB 都引入了 2 taken 的支持,进一步提升了吞吐。

Neoverse N1 是基于 Cortex A76 设计的,Neoverse V1 是基于 Cortex X1 设计的,中间还隔了一代 Cortex A77,根据官方信息,它的 1-cycle latency L1 BTB(即 Nano BTB)容量从 Cortex A76 的 16 变成了 64,main BTB 从 6K 扩到了 8K,而没有提 Micro BTB。同时也没有提到 two taken 的事情。由此推断,Cortex A77 扩大了 Nano BTB 和 Main BTB 的容量,去掉了 Micro BTB,因为 Nano BTB 的容量已经和原来 Neoverse N1 的 Micro BTB 一样大了,其他应该没有变化。再结合 Arm® Cortex®‑A77 Core Technical Reference Manual 可知它的 BTB index 是 [15:4],每个 entry 是 82 bits,位宽和 Neoverse N1 一致,所以应该只是扩了容量。

再往前找 Cortex A73 和 Cortex A75,根据 Chips and Cheese 的实验数据,Cortex A73 有两级 BTB,第一级 BTB 是 48-entry 2-cycle latency(奇怪的是,官方信息 中声称是 64 entry Micro BTAC,容量对不上,但还是更加相信实验数据),第二级 BTB 是 3K-entry 3-cycle latency。根据 Chips and Cheese 的实验数据,Cortex A75 有两级 BTB,第一级 BTB 是 32-entry 1-cycle latency,第二级 BTB 是 3K-entry 3-cycle latency。

下面是一个对比表格:

uArch Cortex A73 Cortex A75 Neoverse N1 Cortex A77 Neoverse V1 Neoverse N2
Nano BTB size N/A 32 branches 16 branches 64 branches 48*2 branches 32*2 branches
Nano BTB latency N/A 1 cycle 1 cycle 1 cycle 1 cycle 1 cycle
Nano BTB throughput N/A 1 branch 1 branch 1 branch 1-2 branches 1-2 branches
Micro BTB size 48 branches N/A 64 branches N/A N/A N/A
Micro BTB latency 2 cycles N/A 2 cycles N/A N/A N/A
Micro BTB throughput 1 branch N/A 1 branch N/A N/A N/A
Main BTB size 3K branches 3K branches 3K*2 branches 4K*2 branches 4K*2 branches 4K*2 branches
Main BTB latency 3 cycles 3 cycles 2-3 cycles 2-3 cycles 2 cycle 2 cycle
Main BTB throughput 1 branch 1 branch 1 branch 1 branch 1-2 branches 1-2 branches
Main BTB area (bits) ? ? 3K*82=251904 4K*82=335872 4K*92=376832 4K*92=376832
Main BTB area (KiB) ? ? 30.75 41 46 46
Technology Node 10nm 10nm 7nm 7nm 5nm 5nm

由此可以看出 ARM 在 BTB 上的优化脉络:

  • Cortex A75 相比 Cortex A73:
    • 降低第一级 BTB 的延迟,从 2 周期的 Micro BTB 变成 1 周期的 Nano BTB,从而降低无条件分支的延迟,代价是容量变小了一些
  • Neoverse N1 相比 Cortex A75:
    • 把 Nano BTB 拆分成 Nano 和 Micro 两级,从而增加容量
    • 通过引入 BTB 压缩优化(即一个 Entry 可以保存 1-2 条分支),使得 Main BTB 能容纳更多的分支
    • 针对 Main BTB 引入 fast path,当只有一个 way 匹配时,只需要 2 周期即可提供预测
  • Cortex A77 相比 Neoverse N1:
    • 通过扩大 Nano BTB,去掉了 Micro BTB,让更多分支可以享受 1 周期的延迟
    • 继续扩大 Main BTB
  • Neoverse V1 相比 Cortex A77:
    • 在 Nano 和 Main BTB 上引入 two taken 预测,增加吞吐
    • 减少 Main BTB 延迟,从 2-3 周期变成固定 2 周期

注:各代 Cortex 与 Neoverse 对应关系以及代号:

  • Cortex-A73(Artemis)
  • Cortex-A75(Prometheus)
  • Cortex-A76(Enyo)/Neoverse-N1(Ares)
  • Cortex-A77(Deimos)
  • Cortex-A78(Hercules)/Cortex-X1(Hera)/Neoverse-V1(Zeus, based on Cortex-X1)
  • Cortex-A710(Matterhorn)/Cortex-X2(Matterhorn ELP)/Neoverse-N2(Perseus, based on Cortex-A710)

🔲 ☆

ARM Neoverse N1 (代号 Ares) 的 BTB 结构分析

ARM Neoverse N1 (代号 Ares) 的 BTB 结构分析

本文同步发布到本人的知乎

背景

ARM Neoverse N1 是 2019 年发布的比较早的一代 ARM 服务器的处理器,它在很多地方都和 Cortex-A76 类似。它的 BTB 结构比较有意思,所以在这里对它的 BTB 做一些分析。

官方信息

首先收集了一些 ARM Neoverse N1 的 BTB 结构的官方信息:

简单整理一下官方信息,大概有三级 BTB:

  • 16-entry Nano BTB, 1 cycle latency (0 cycle bubble)
  • 64-entry Micro BTB
  • 6K-entry Main BTB, 3 cycle latency

但是很多细节是缺失的,因此下面结合微架构测试,进一步研究它的内部结构。

微架构测试

在之前的博客里,我们已经测试了各种处理器的 BTB,在这里也是一样的:按照一定的 stride 分布无条件直接分支,构成一个链条,然后测量 CPI。

stride=4B

首先是 stride=4B 的情况:

可以看到,图像上出现了三个比较显著的台阶:

  • 第一个台阶到 16 条分支,CPI=1,对应了 16-entry 的 Nano BTB
  • 第二个台阶到 80 条分支,CPI=2,其中 80=16+64,多出来的部分对应了 64-entry 的 Micro BTB,意味着 Micro BTB 相对 Nano BTB 是 Victim BTB 的地位:Nano BTB 被替换出去的 entry 会进入到 Micro BTB,而命中 Micro BTB 的 entry 会被移动到 Nano BTB
  • 第三个台阶到 8192 条分支,CPI=5,大于 Main BTB 的 3 cycle latency,说明此时没有命中 Main BTB,而是要等到取指和译码后,计算出正确的目的地址再回滚,导致了 5 cycle latency;8192 的性能下降原因还需要进一步研究,16384 的性能下降对应了 64KB 的 ICache,因为 4B*16384=64KB

那么 stride=4B 的情况下就遗留了两个问题:为什么没有命中 Main BTB;8192 处为什么出现了性能下降。

stride=8B

接下来观察 stride=8B 的情况:

可以看到,图像上出现了三个比较显著的台阶:

  • 第一个台阶到 16 条分支,CPI=1,对应了 16-entry 的 Nano BTB,和之前一样
  • 第二个台阶到 80 条分支,CPI=2,其中 80=16+64,多出来的部分对应了 64-entry 的 Micro BTB,和之前一样
  • 第三个台阶到 4096 条分支,CPI=2.75,约等于 Main BTB 的 3 cycle latency,说明此时命中的是 Main BTB,但是它并没有达到宣称的 6144-entry 的 Main BTB 容量;8192 还有一个性能下降,这对应了 64KB 的 ICache:8B*8192=64KB

相比 stride=4B,Nano BTB 和 Micro BTB 的行为没有变化;而 Main BTB 开始能够命中,这代表 Main BTB 在分支特别密集的情况下,会出现性能问题。

那么 stride=8B 的情况下遗留了两个问题:为什么 CPI=2.75 而不是 3?为什么只观察到了 4K 的 Main BTB 容量,而不是 6K?

stride=16B

继续观察 stride=16B 的情况:

可以看到,图像上出现了四个比较显著的台阶:

  • 第一个台阶到 16 条分支,CPI=1,对应了 16-entry 的 Nano BTB,和之前一样
  • 第二个台阶到 80 条分支,CPI=2,其中 80=16+64,多出来的部分对应了 64-entry 的 Micro BTB,和之前一样
  • 第三个台阶到 4096 条分支,CPI=2.5,比 Main BTB 的 3 cycle latency 略小,但是大于 2,说明此时命中的是 Main BTB,此时遇到了 64KB ICache 的瓶颈:4096*16B=64KB
  • 第四个台阶到 6144 条分支,CPI=3.5,比 Main BTB 的 3 cycle latency 略大,是因为 64KB ICache 出现了缺失,但这个时候终于显现了宣传的 Main BTB 的 6144 的容量

相比 stride=8B,Nano BTB 和 Micro BTB 的行为没有变化;Main BTB 的 6144 容量开始显现,并且出现地比 64KB ICache 更晚。

那么 stride=16B 的情况下遗留了一个问题:为什么出现了 CPI=2.5 的平台?

stride=32B

继续观察 stride=32B 的情况:

可以看到,图像上出现了三个比较显著的台阶:

  • 第一个台阶到 16 条分支,CPI=1,对应了 16-entry 的 Nano BTB,和之前一样
  • 第二个台阶到 2048 条分支,CPI=2,此时遇到了 64KB ICache 的瓶颈:2048*32B=64KB,但是这个时候已经超出了 Micro BTB 的容量,而 Main BTB 有 3 cycle 的 latency,为何还能保持 CPI=2 呢
  • 第三个台阶到 6144 条分支,CPI=4,比 Main BTB 的 3 cycle latency 略大,是因为 64KB ICache 出现了缺失,显现的是宣传的 Main BTB 的 6144 的容量,更加说明第二个台阶内 Main BTB 是命中的

那么 stride=32B 的情况下遗留了一个问题:为什么在 Main BTB 的范围内出现了 CPI=2 的平台?

stride=64B

继续观察 stride=64B 的情况:

可以看到,图像上出现了三个比较显著的台阶:

  • 第一个台阶到 16 条分支,CPI=1,对应了 16-entry 的 Nano BTB,和之前一样
  • 第二个台阶到 1024 条分支,CPI=2,此时遇到了 64KB ICache 的瓶颈:1024*64B=64KB,和之前一样
  • 第三个台阶到 3122 条分支,CPI=6,比 Main BTB 的 3 cycle latency 大,是因为 64KB ICache 出现了缺失,此时 Main BTB 的容量砍半

stride=64B 相比 stride=32B 的 Main BTB 容量砍半,这是组相连的表现:如果 PC[5] 在组相连的 index 当中,那么当 stride=64B 时,PC[5] 恒等于 0,意味着只有一半的 set 可以被用到,那也就只有一半的容量了。

Nano BTB 和 Micro BTB 容量没有变小,意味着它们大概率是全相连的:这也和它们的大小相吻合。

那么 stride=64B 的情况下遗留的问题和 stride=32B 一样:为什么在 Main BTB 的范围内出现了 CPI=2 的平台?

stride=128B

继续观察 stride=128B 的情况:

可以看到,图像上出现了三个比较显著的台阶:

  • 第一个台阶到 16 条分支,CPI=1,对应了 16-entry 的 Nano BTB,和之前一样
  • 第二个台阶到 512 条分支,CPI=2,此时遇到了 64KB ICache 的瓶颈:512*128B=64KB,和之前一样
  • 第三个台阶到 1536 条分支,CPI=6.x,比 Main BTB 的 3 cycle latency 大,是因为 64KB ICache 出现了缺失,此时 Main BTB 的容量进一步砍半

stride=128B 相比 stride=64B 的 Main BTB 容量进一步砍半,也是组相连的表现,意味着 PC[6] 也在组相连的 idnex 当中,只有四分之一的 set 可以被用到。

那么 stride=128B 的情况下遗留的问题和 stride=32B 一样:为什么在 Main BTB 的范围内出现了 CPI=2 的平台?

小结

测试到这里就差不多了,更大的 stride 得到的也是类似的结果,总结一下前面的发现:

  • Nano BTB 是 16-entry,1 cycle latency,不随着 stride 变化
  • Micro BTB 是 64-entry,2 cycle latency,也不随着 stride 变化
  • Main BTB 是 6K-entry,3 cycle latency,容量随着 stride 变化,大概率是 PC[n:5] 这一段被用于 index,使得 stride=64B 开始容量不断减半
  • 64KB ICache 很多时候会比 Main BTB 更早成为瓶颈

也总结一下前面发现了各种没有解释的遗留问题:

  • stride=4B 的情况下,Main BTB 没有像预期那样工作:解释见后
  • stride=4B 的情况下,8192 条分支处出现了性能下降:暂无解释
  • stride=8B 的情况下,只观察到 4096 的 Main BTB 容量,而不是 6144:解释见后
  • stride=8B 的情况下,在 Main BTB 命中的范围内,CPI=2.75:解释见后
  • stride=16B 的情况下,在 Main BTB 命中的范围内,CPI=2.5:解释见后
  • stride=32B 或 64B 或 128B 的情况下,在 Main BTB 命中的范围内,CPI=2:解释见后

接下来尝试解析一下这些遗留问题背后的原理。部分遗留问题,并没有被解释出来,欢迎读者提出猜想。

解析遗留问题

stride=4B 的情况下,Main BTB 没有像预期那样工作

对于这种类似 Cache 的结构,当它看起来总是没有命中的时候,其实就是每一个 Set 内要访问的数据超出了 Way,导致每次新访问的都会缺失。上面分析到,Main BTB 的 Index 大概是 PC[n:5] 这一段,那么一个对齐的 32B 范围内,分支指令都会被映射到同一个 set 内。当 stride=4B 的时候,对齐的 32B 范围内有 8 条指令;而 stride=8B 的时候,只有 4 条指令。8 条指令不行,4 条指令可以,暗示了中间跨越了 Way 的数量。

首先来回顾一下 Main BTB 的 6144-entry 是怎么来的:虽然它没说是几路组相连,但因为 6144 有一个 3 的因子,它不是二的幂次,所以一定是在 Way 数量上产生的。这就导致了至少这样几种可能:

  • 3-way set associative, 2048 sets
  • 6-way set associative, 1024 sets
  • 12-way set associative, 512 sets

回顾前面的分析:4 条指令没有超过 Way 数量,8 条指令超过了,那么只能是上述可能里的 6-way set associative,1024 sets 的情况。

翻阅 Arm® Neoverse™ N1 Core Technical Reference Manual,它是这么说的:

L1 BTB data location encoding:

  • [31:24]: RAMID = 0x02
  • [23:20]: Reserved
  • [19:18]: Way
  • [17:15]: Reserved
  • [14:5]: Index [14:5]
  • [4:0]: Reserved

它暗示了 L1 BTB(也就是 Main BTB)的 Index 是 PC[14:5],这和我们之前的观察一致。这样算出来有 2^(14-5+1)=1024 个 set,和我们前面的 6-way set associative,1024 sets 的猜测是一致的。

但是,这时候又出现一个问题:[19:18] 只能记录两位的 Way 编号,也就是说不能超过 4 个 Way,但实际上有 6 个 Way。这似乎又出现了矛盾。

继续去阅读文档里对从 BTB 读出来的数据的描述:

L1 BTB cache format:

  • Instruction Register 0 [63:0]: Data [63:0]
  • Instruction Register 1 [17:0]: Data [81:64]

这暗示了给定一个 Index 和一个 Way,可以读出来 82 bit 的数据,这不太寻常:一个分支的信息,通常不需要这么多 bit 的数据。一个 BTB Entry,通常需要这些信息:

  • valid
  • branch type: conditional or unconditional, direct or indirect, call or return, etc.
  • tag
  • replacement policy
  • part of target address

除非保存了完整的 target address 和 tag,是达不到 82 bit 这么多的。但是这样又显得很浪费,可能还有其他的可能性。

考虑到上面出现的两位的 Way 编号,并且有 3 的素数因子,只能是 3-way 组相连了。如果按 3-way 组相连,1024 个 set 来算,只有 3072 个 entry,距离 Main BTB 的容量 6144 个 entry 刚好只有一半。一个想法诞生了:如果一个 BTB entry 可以保存两个分支的信息呢?82 bit 正好是 2 的倍数,除以二是 41 bit,每个分支存 41 bit 的数据是比较合理的数据。这样,就可以推导出来,它 Main BTB 的组织方式是:

  • Index: PC[14:5],有 1024 个 set
  • 3-Way 组相连
  • 每个 Entry 是 82 bit,可以记录两条分支的信息

所以 BTB 的 Entry 怎么计算其实会比较复杂,到底是按实际的 Entry 数,还是按分支数,需要深入分析才能理解。

那么,为什么要把两条分支保存在一个 BTB Entry 里呢?Neoverse N1 并没有实现 two taken,似乎并没有放在一起的必要。而且虽然是 3-Way 组相连,匹配的时候还是 6-Way 的,那么这样做的好处是什么呢?

这时候就要提到很多处理器实现的一个优化了:大多数分支,它的目的地址距离它自己是很短的,即使考虑指令支持的最大范围,比如 AArch64 指令里面,B 指令的立即数是 26 位,B.cond 和 CBNZ 的立即数是 19 位,也比完整的虚拟地址空间小很多。针对多数的跳转距离比较短的分支,可以用一个更压缩的表示来保存,使得 BTB 可以保存更多的分支;同时,也保留针对跳转距离比较长的分支的支持。这和前面的这个设计就对上了:对于跳转距离短的分支,每 41 bit 可以保存一条分支的信息;对于跳转距离远的分支,再用 82 bit 来保存一条分支的信息。从另外一个角度来说,41 bit 也确实保存不下完整的虚拟地址,所以需要有一个方案给跳转距离远的分支兜底。

那么如果跳转距离比较远,Main BTB 的容量将会只有一半。感兴趣的读者可以设计实验来验证这一点。

小结:Main BTB 是 1024 set,3 way set associative 的结构,一共 3072 个 entry,每个 entry 可以保存两条分支,Index 是 PC[14:5]。stride=4B 的情况下,会出现一个 set 内 8 条分支的情况,无法在 3 个 entry 内放下,所以总是会出现缺失。一共占用 3072*82=251904 bit 也就是 30.75 KB 的空间。

stride=8B 的情况下,只观察到 4096 的 Main BTB 容量,而不是 6144

在 stride=8B 的情况下,只观察到 4096 的 Main BTB 容量,实际上,用刚才分析的 Main BTB 结构,就可以分析出来。

首先,这个测试的构造方法是,给定分支数和 stride,按照这个 stride 在连续的一段虚拟地址上分布这些分支。以 stride=8B 为例,那么分支 i 的地址就是 8*i(实际情况下高位不是 0,但是所有的分支的高位是相同的,例如 0x100000000+8*i,但这不影响分析)。我们来观察一下前几个分支的信息:

  • Branch 0: addr=0x00, index=0
  • Branch 1: addr=0x08, index=0
  • Branch 2: addr=0x10, index=0
  • Branch 3: addr=0x18, index=0
  • Branch 4: addr=0x20, index=1

可以看到从分支 5 开始,到了一个新的 set,第一个 set 内出现了 4 条分支,小于一个 set 内可以保存的最多 6 条分支。接下来看从分支 4096 开始的几个分支:

  • Branch 4096: addr=0x8000, index=0
  • Branch 4097: addr=0x8008, index=0
  • Branch 4098: addr=0x8010, index=0
  • Branch 4099: addr=0x8018, index=0
  • Branch 4100: addr=0x8020, index=1

可以看到,index=0 这个 set 出现了 8 个 Branch:Branch 0-3 和 Branch 4096-4099,已经大于一个 set 内可以保存的最多 6 条分支。虽然 Main BTB 容量是 6144,但由于分支的排布方式,会首先在一个 set 里出现溢出。然后随着分支继续增加,产生溢出的 set 的比例逐渐上升,直到 8192 条分支的时候,每个 set 都完全溢出了。此时也恰好遇到了 64KB ICache 的瓶颈,如果 ICache 更大,应该会在 8192 的地方观察到一个平台,此时 Main BTB 完全缺失。

继续增加 stride,就没有了这个问题。以 stride=16B 为例子,Branch i 地址是 i*16,那么这些分支的地址是:

  • Branch 0: addr=0x00, index=0
  • Branch 1: addr=0x10, index=0
  • Branch 2: addr=0x20, index=1
  • Branch 3: addr=0x30, index=1
  • ...
  • Branch 2048: addr=0x8000, index=0
  • Branch 2049: addr=0x8010, index=0
  • Branch 2050: addr=0x8020, index=1
  • Branch 2051: addr=0x8030, index=1
  • ...
  • Branch 4096: addr=0x10000, index=0
  • Branch 4097: addr=0x10010, index=0
  • Branch 4098: addr=0x10020, index=1
  • Branch 4099: addr=0x10030, index=1
  • ...
  • Branch 6144: addr=0x18000, index=0
  • Branch 6145: addr=0x18010, index=0
  • Branch 6146: addr=0x18020, index=1
  • Branch 6147: addr=0x18030, index=1

这个时候,每个 set 会以两个 branch 的粒度来增加,由于 6 是 2 的倍数,所以从 4096 开始,set 逐渐被填满,会等到 6144 条分支才会产生溢出。

小结:由于 Main BTB 的 Index 是 PC[14:5],所以在 stride=8B 的情况下,每个 set 内以 4 个 branch 的粒度来增加,会有部分 set 已经出现溢出(只能存 6 个分支,但需要存 8 个分支),而另一部分 set 还没有满的情况(能存 6 个分支,但只存了 4 个分支)。这个拐点就是 4096 条分支。

stride=8B 的情况下,在 Main BTB 命中的范围内,CPI=2.75;stride=16B 的情况下,在 Main BTB 命中的范围内,CPI=2.5;stride=32B 或 64B 或 128B 的情况下,在 Main BTB 命中的范围内,CPI=2

前面提到,命中 Main BTB 的时候,其实 CPI 并不是 3,而是 2 到 3 之间的一个数,这似乎意味着 Main BTB 并非总是 3 周期提供一个预测。考虑到 Main BTB 容量比较大,很难单周期提供一个预测,猜测 Main BTB 可以两周期或者三周期提供一个预测。那么为什么会在不同的延迟下给出预测结果呢?

首先来分析一下 Main BTB 是如何做预测的:它首先会用传入的 VA 访问 SRAM,得到 3 个 82-bit 的数据,里面最多可以存 6 条分支指令的信息。得到这些数据以后,进行 tag 比较,筛选出其中匹配的部分。如果没有匹配的,或者只有一个匹配的分支,那都好说。但是,如果有多条匹配的分支呢?

例如,这是一个对齐的 32B 块,里面有 8 条 4 字节的指令:

0 4 8 12 16 20 24 28 +-----+-----+-----+-----+-----+-----+-----+-----+ | NOP | NOP | Br | NOP | NOP | NOP | Br | NOP | +-----+-----+-----+-----+-----+-----+-----+-----+ 

假如要从地址 4 处开始执行,那么 Main BTB 应该要得到的是位于地址 8 的分支的信息;假如要从地址 16 处开始执行,那么 Main BTB 应该要得到的是位于地址 24 的分支的信息。为了实现这个事情,硬件上应该:

  • 首先找到在同一个 32B 块内的所有分支:通过 tag 比对,找到这个 set 内的在该 32B 内的所有分支
  • 接着,找到比输入的 VA 大于或者等于第一个分支

这个逻辑是比较复杂的,首先要筛选出地址大于或等于输入的 VA 的分支,其次要找到其中 VA 最小的分支。一个思路是保证 BTB 里面的 VA 是排好序的,但是硬件上排序并不好做,而且即使排序了,也需要做类似二分搜索的事情。另一个思路就是不管顺序,用组合逻辑把所有可能性都考虑到,计算出要找的分支。

但是这个组合逻辑比较复杂,本质上就是一个 filter+min 操作,需要比较大的延迟。三个周期能做下来,但是两个周期内,就做不下这么复杂的组合逻辑了。那怎么办呢?

观察一下 CPI 比 3 小的情况:

  • stride=8B 的 CPI=2.75
  • stride=16B 的 CPI=2.5
  • stride=32B 或 64B 或 128B 的 CPI=2

可以看到,随着 stride 增加,CPI 逐渐减少,到 stride=32B 的时候,能够稳定地达到 CPI=2 的情况。设想 Main BTB 有一个 2 周期出结果的 fast path,那么它此时可以稳定地触发;而 stride=16B 只有一半的时候可以触发 fast path:0.5*2+0.5*3=2.5;stride=8B 只有四分之一的时候可以触发 fast path:0.25*2+0.75*3=2.75。这样这些 CPI 都说得通了,其实就是有多大的概率能够触发 fast path。那么 fast path 生效的比例是:

  • stride=8B 有四分之一的概率走 fast path
  • stride=16B 有二分之一的概率走 fast path
  • stride=32B 或 64B 或 128B 一定可以走 fast path

此时你可能已经发现了一些规律:32/4=8,然后 32/2=16。也就是说,当对齐的 32B 块里,有四条分支的时候,平均只有一条分支可以走 fast path;有两条分支的时候,平均也是一条分支可以走 fast path;只有一条分支的时候,它总是可以走 fast path。

再回想一下前面的匹配逻辑:

  1. 找到该 32B 块内所有的分支:这一步免不了
  2. 找到大于或等于输入 VA 地址的所有分支:这一步也免不了
  3. 找到第一个满足要求的分支:如果只有一条分支,那就不用寻找最小值了,这就是 fast path 的条件

这就解释了前面的现象:当 stride=8B 的时候,对齐的 32B 块内部是:

0 4 8 12 16 20 24 28 +-----+-----+-----+-----+-----+-----+-----+-----+ | Br | NOP | Br | NOP | Br | NOP | Br | NOP | +-----+-----+-----+-----+-----+-----+-----+-----+ 

分支预测的时候,用的地址分别是 0、8、16 和 24。当用 0、8 和 16 的输入 VA 查询的时候,分别能找到 4、3 和 2 条 VA 大于或等于输入 VA 的分支。只有在用 24 的输入 VA 查询的时候,只能找到一条分支,不需要再求 min。

stride=16B 的情况类似,在预测第二条分支的时候,只有一条分支满足要求,可以走 fast path。

stride=32B 或更大的时候,对齐的 32B 块内都只有一条分支,满足走 fast path 的条件。

这就解释了前面看到的各种奇怪的 CPI 现象。

那么,为什么只有 Main BTB 会出现这种现象呢,理论上来说,Nano BTB 和 Micro BTB 也可以做类似的优化?这就涉及到了 BTB 的不同的组织方式:

  1. 一种是 Main BTB 这种,每条分支只保存一份,那么为了找到某个 VA 开始的第一条分支,就需要把满足要求的分支都找出来,再寻找地址最小的那一个;具体实现上,也有两种情况:
    1. 对于每个可能出现分支指令的地址,都进行一次 BTB 查询(这种结构叫 Instruction BTB)
    2. 对于每个对齐的块,记录这个块内的有限条分支的信息(这种结构叫 Region BTB),Main BTB 采用的就是这种,每个对齐的 32B 块内最多保存六条分支
  2. 另一种结构,则是直接记录从某个 VA 开始的第一条分支,即给定 VA,查询 BTB 后,匹配到的 entry 里记录的就是从这个 VA 开始的第一条分支(这种结构叫做 Block BTB);这样一条分支可能会出现在多个 entry 内,此时就不会涉及到上面所述的 fast path 优化

那么猜测 Nano BTB 和 Micro BTB 采用了 Block BTB 的方法,或者因为延迟本身就足够低,即使可以做 fast path,也没有引入 fast path 优化。

详细的 BTB 设计分析,可以参考 浅谈乱序执行 CPU(三:前端) 的相关内容。

小结:Main BTB 可以在 2 或 3 周期提供预测,其中 2 周期预测的条件是,只找到一条 VA 大于或等于输入 VA 的分支,此时可以跳过求 min 的组合逻辑,在第二个周期给出预测。

模拟

既然已经知道了它的 BTB 结构,就写了一段程序来模拟它的工作过程:

// Cortex-A76/Neoverse-N1 BTB model // 16-entry Nano BTB, fully associative, 1 cycle latency. // 64-entry Micro BTB, fully associative, 2 cycle latency. // 3072-entry Main BTB, 3-way set associative, 2-3 cycle latency, each entry at // most 2 branches, index PC[14:5].  #include <assert.h> #include <set> #include <stdint.h> #include <stdio.h> #include <string.h> #include <utility> #include <vector>  struct BTBEntry {  bool valid;  uint64_t pc;  uint64_t target; };  typedef BTBEntry NanoBTBEntry; typedef BTBEntry MicroBTBEntry; typedef BTBEntry MainBTBEntry;  struct Model {  NanoBTBEntry nanoBTB[16];  MicroBTBEntry microBTB[64];  // pretend as 6-way  MainBTBEntry mainBTB[1024][6];  // 64KB ICache, pretend as direct mapped  // index: PC[15:6]  // tag: PC[:16]  uint64_t iCache[64 * 1024 / 64];   // return latency  // use pc to predict a branch at pc, i.e. pva = pc  int match(uint64_t pc, uint64_t target) {  int result = 5; // BTB miss penalty  // Nano BTB at P1  for (int i = 0; i < 16; i++) {  if (nanoBTB[i].pc == pc && nanoBTB[i].target == target) {  // Nano BTB hit  // LRU: move it to head  for (int j = i; j > 0; j--) {  nanoBTB[j] = nanoBTB[j - 1];  }  nanoBTB[0].pc = pc;  nanoBTB[0].target = target;  result = 1;  goto main_btb;  }  }   // Nano BTB miss, check Micro BTB at P1  // like victim cache  for (int i = 0; i < 64; i++) {  if (microBTB[i].pc == pc && microBTB[i].target == target) {  // Micro BTB hit  // Move to Nano BTB  for (int j = i; j > 0; j--) {  microBTB[j] = microBTB[j - 1];  }  microBTB[0].pc = nanoBTB[16 - 1].pc;  microBTB[0].target = nanoBTB[16 - 1].target;   for (int j = 16 - 1; j > 0; j--) {  nanoBTB[j] = nanoBTB[j - 1];  }  nanoBTB[0].pc = pc;  nanoBTB[0].target = target;  result = 2;  goto main_btb;  }  }   // Micro BTB miss  for (int j = 64 - 1; j > 0; j--) {  microBTB[j] = microBTB[j - 1];  }  microBTB[0].pc = nanoBTB[16 - 1].pc;  microBTB[0].target = nanoBTB[16 - 1].target;   for (int j = 16 - 1; j > 0; j--) {  nanoBTB[j] = nanoBTB[j - 1];  }  nanoBTB[0].pc = pc;  nanoBTB[0].target = target;   main_btb:  // check Main BTB  // PC[4:2]  uint64_t offset = pc & 0x1c;  // PC[14:5]  uint64_t index = (pc & 0x7fe0) >> 5;  assert(index < 1024);  // PC[n:15]  uint64_t tag = pc >> 15;  uint64_t min_offset = -1;  int min_i = -1;  int matches = 0;  for (int i = 0; i < 6; i++) {  // find matches  if ((mainBTB[index][i].pc >> 15) == tag && mainBTB[index][i].valid) {  // check offset  if ((mainBTB[index][i].pc & 0x1c) >= offset) {  if (min_i == -1) {  min_i = i;  min_offset = mainBTB[index][i].pc & 0x1c;  } else if ((mainBTB[index][i].pc & 0x1c) < min_offset) {  min_i = i;  min_offset = mainBTB[index][i].pc & 0x1c;  }  matches++;  }  }  }   // hit  if (min_offset == offset) {  if (matches != 0) {  // LRU  MainBTBEntry temp = mainBTB[index][min_i];  for (int i = min_i; i > 0; i--) {  mainBTB[index][i] = mainBTB[index][i - 1];  }  mainBTB[index][0] = temp;  }   if (matches == 1) {  // fast path  if (result == 5) {  result = 2;  goto end;  }  } else if (matches > 1) {  // slow path  if (result == 5) {  result = 3;  goto end;  }  }  }   // miss  for (int i = 6 - 1; i > 0; i--) {  mainBTB[index][i] = mainBTB[index][i - 1];  }  mainBTB[index][0].pc = pc;  mainBTB[index][0].target = target;  mainBTB[index][0].valid = true;   end:  // BTB miss   // compute icache penalty  index = (pc >> 6) % 1024;  tag = pc >> 16;  if (iCache[index] != tag) {  // extra 4 cycle penalty  result += 4;  iCache[index] = tag;  }   return result;  } };  int main() {  FILE *fp = fopen("btb_size.csv", "w");  uint64_t min_size = 2;  uint64_t max_size = 16384;  uint64_t max_product = 1048576;  std::vector<int> mults = {1, 3, 5, 7, 9, 11, 13, 15, 17, 19,  21, 23, 25, 27, 29, 31, 33, 35, 37, 39};   fprintf(fp, "pattern,size,stride,min,avg,max\n");  for (uint64_t stride = 4; stride <= 128; stride *= 2) {  std::set<int> sizes;  for (uint64_t size_base = min_size; size_base <= max_product / stride;  size_base *= 2) {  for (uint64_t mult : mults) {  for (uint64_t size = size_base * mult - 1;  size <= size_base * mult + 1 && size * stride <= max_product &&  size <= max_size;  size++) {  sizes.insert(size);  }  }  }   for (int size : sizes) {  Model model;  memset(&model, 0, sizeof(model));  int cycles = 0;  int branch_count = 1000 * size;  // warmup  for (int i = 0; i < branch_count; i++) {  uint64_t pc = ((i % size) * stride);  uint64_t target = (((i + 1) % size) * stride);  model.match(pc, target);  }   // test  for (int i = 0; i < branch_count; i++) {  uint64_t pc = ((i % size) * stride);  uint64_t target = (((i + 1) % size) * stride);  cycles += model.match(pc, target);  }  float cpi = (float)cycles / branch_count;  fprintf(fp, "0,%d,%d,%.2f,%.2f,%.2f\n", size, stride, cpi, cpi, cpi);  fflush(fp);  }  }  return 0; } 

这个模型只评估了 BTB 和 L1 ICache 的性能影响。下面是模拟和实际的对比图,左边是模拟,右边是实际:

stride=4B:

stride=8B:

stride=16B:

stride=32B:

stride=64B:

stride=128B:

可以看到模型和实际的表现是非常一致的。

总结

最后总结一下 Neoverse N1 的 BTB:

  • 16-entry Nano BTB, fully associative, 1 cycle latency
  • 64-entry Micro BTB, fully associative, 2 cycle latency, behave as victim BTB of Nano BTB
  • 3072-entry(6144 branches) Main BTB, 3-way(6-branch-way) set associative, 2-3 cycle latency, each entry at most 2 branches, index PC[14:5]

🔲 ☆

Linux 的性能分析(Perf)实现探究

Linux 的性能分析(Perf)实现探究

背景

最近使用 Linux 的性能分析功能比较多,但是很少去探究背后的原理,例如硬件的 PMU 是怎么配置的,每个进程乃至每个线程级别的 PMU 是怎么采样的。这篇博客尝试探究这背后的原理。

PMU

硬件

支撑性能分析的背后是硬件提供的机制,最常用的就是性能计数器:硬件会提供一些可以配置的性能计数器,在对应的硬件事件触发是,更新这些计数器,然后再由程序读取计数器的值并统计。下面以 ARM 为例,分析一下硬件提供的性能计数的接口:

  1. Cycle 计数器:Cycle Counter Register(PMCCNTR_EL0) 和 Cycle Count Filter Register(PMCCFILTR_EL0),其中后者控制前者在什么特权态下会进行计数
  2. 最多 31 个通用性能计数器:
    1. 该性能计数器记录的硬件事件以及计数的条件:PMEVTYPER_EL0,n 取 0 到 31
    2. 该性能计数器当前的值:PMEVCNTR_EL0,n 取 0 到 31
  3. 控制 Cycle 计数器和通用性能计数器的状态:PMCNTENCLR_EL0/PMCNTENSET_EL0/PMCR_EL0
  4. 各计数器是否溢出:PMOVSCLR_EL0/PMOVSSET_EL0
  5. 当计数器溢出时,PMU 会拉起中断,针对这些中断的配置:PMINTENCLR_EL1/PMINTENSET_EL1

注:实际上,由于经常会对指令数进行采样,ARM v9.4/8.9 允许硬件实现一个额外的指令计数器,和 Cycle 计数器类似。

如果想要在用户态频繁地读取性能计数器(cap_user_rdpmc),避免频繁进入内核的开销,也可以在用户态中直接读取性能计数器 PMCCNTR_EL0/PMEVCNTR_EL0:内核在 PMUSERENR_EL0 中进行相应的权限配置即可。v3.9 或更高版本的 PMU 实现允许按照每个 counter 的粒度来控制用户态是否允许访问(PMUACR)。

LoongArch 也是类似的,其接口更简单:它只有通用性能计数器,有如下的 csr 来配置各个性能计数器:

  1. 性能计数器的值:perfcntr
  2. 性能计数器的配置:perfctrl,有如下字段:
    1. EVENT: 事件编号
    2. PLV{0,1,2,3}: 特权态过滤,对应四个特权态下是否采样
    3. IE:是否启用溢出中断
  3. misc.rpcntl3:允许用户态程序读取性能计数器

内核驱动

在 Linux 内核中,负责控制 ARM 性能计数接口的代码在 arm_pmuv3.c 当中。根据这个硬件接口,可以预想到,如果要对一段程序观察它在某个计数器上的取值,需要:

  1. 分配一个性能计数器,可能是 Cycle 计数器或者通用性能计数器:对应 armv8pmu_get_event_idx 函数,先分配 Cycle 计数器,再从剩下的通用性能计数器中找到一个空闲的
  2. 配置并启用该性能计数器,对应 armv8pmu_enable_event 函数:
    1. 把事件类型写入到 PMEVTYPER_EL0 中,对应 armv8pmu_write_event_type 函数
    2. 启用事件对应的溢出中断,写入 PMINTENSET_EL1,对应 armv8pmu_enable_event_irq 函数
    3. 事件开始计数,写入 PMCNTENSET_EL0,对应 armv8pmu_enable_event_counter 函数
  3. 在程序开始前,从 PMEVCNTR_EL0 读取一次计数器的当前取值,对应 armv8pmu_read_counter 函数
  4. 在程序结束时,再读取一次计数器的当前取值,和程序开始时的值求差
  5. 为了解决溢出的问题:配置中断,在溢出时会进入中断处理代码,统计溢出次数,计入差值的高位,对应 armv8pmu_handle_irq 函数

Perf 子系统

除了由单独的架构相关的内核驱动负责配置硬件以外,还需要由 Perf 子系统来处理来自用户的 perf 使用。具体地,内核驱动会注册一个 struct pmu 给 Perf 子系统,实现这些函数:

// Fully disable/enable this PMUvoid (*pmu_enable) (struct pmu *pmu); /* optional */void (*pmu_disable) (struct pmu *pmu); /* optional */// Try and initialize the event for this PMU.int (*event_init) (struct perf_event *event);// Adds/Removes a counter to/from the PMUint (*add) (struct perf_event *event, int flags);void (*del) (struct perf_event *event, int flags);// Starts/Stops a counter present on the PMU.void (*start) (struct perf_event *event, int flags);void (*stop) (struct perf_event *event, int flags);// Updates the counter value of the event.void (*read) (struct perf_event *event);

可见在内核里,PMU 计数器的抽象是 struct perf_event,这个是架构无关的,根据用户态程序通过 perf_event_open 构造出来的;内核驱动就会根据这个 struct perf_event 去进行实际的硬件计数器的配置。例如用户程序在 struct perf_event_attr 里设置 exclude_kernel = 1,就会传到 struct perf_event 当中,最后在相应的内核驱动中,变成硬件性能计数器配置里,计数时忽略内核所在特权态的配置。

perf record 是基于采样实现的:当性能计数器溢出(一般是 Cycle 计数器)的时候,触发中断进入内核,此时由软件来收集被打断的程序的上下文信息,收集完成后再回到被打断的程序继续执行。

虚拟化

在虚拟化场景下,依然希望虚拟机内的 OS 可以有性能计数器可以用,同时宿主机上也可能希望获取虚拟机的性能计数器信息。

一种方法是以纯软件的方法去实现性能计数器,比如 QEMU 的 TCG 模式,可以模拟出一个以固定频率运行的处理器的 Cycle 计数器,但实际上就是拿时间除以频率,是假的性能计数器;此外还能模拟出 Instruction 计数器,因为 QEMU 在做指令翻译的时候,可以顺带记录下执行的指令数;而微架构相关的性能计数器就没法靠这个来实现了。

另一种方法则是在硬件虚拟化的基础上,让虚拟机享受到性能计数器。不过为了安全性,宿主机可以获取虚拟机的性能计数器,但反过来,虚拟机的性能计数器不应该得到宿主机的信息。

目前 LoongArch KVM 已经支持性能计数器的虚拟化,下面来看它是怎么做的:

  1. 给宿主机(host)维护一份性能计数器的上下文,用 kvm_save_host_pmu 保存,用 kvm_restore_host_pmu 恢复
  2. 再给每个虚拟机(guest)维护一份性能计数器的上下文,此时为了访问虚拟机的 csr,要访问 gcsr(guest csr):用 kvm_save_guest_pmu 保存,用 kvm_restore_guest_pmu
  3. 当虚拟机(guest)因为各种原因回到了 VMM,就要进行上下文切换,保存虚拟机的性能计数器,恢复宿主机的性能计数器;同理,进入虚拟机时,再次进行上下文切换,保存宿主机的性能计数器,恢复虚拟机的性能计数器

Intel PT

Intel PT 是 Intel 平台上跟踪指令流的机制,它可以记录这些信息:

  1. 页表的修改
  2. 时钟周期
  3. 分支跳转
  4. 功耗状态变化

perf 工具也是支持用 Intel PT 进行跟踪的:perf-intel-pt(1) — Linux manual page,下面的命令用 Intel PT 跟踪一条命令的执行过程,并显示出它生成的跟踪信息:

perf record -e intel_pt//u ls# itrace: instruction trace# i: instructions events# y: cycles events# b: branches events# x: transactions events# w: ptwrite events# p: power events# e: error eventsperf script --itrace=iybxwpe

比如写一个循环 10 次的代码:

int main() { for (int i = 0; i < 10; i++) { }}

对应的汇编:

0000000000001129 <main>: 1129: 55 push %rbp 112a: 48 89 e5 mov %rsp,%rbp 112d: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp) 1134: eb 04 jmp 113a <main+0x11> 1136: 83 45 fc 01 addl $0x1,-0x4(%rbp) 113a: 83 7d fc 09 cmpl $0x9,-0x4(%rbp) 113e: 7e f6 jle 1136 <main+0xd> 1140: b8 00 00 00 00 mov $0x0,%eax 1145: 5d pop %rbp 1146: c3 ret

按照上述方法,可以看到它生成了各个分支跳转的信息:

test1 3772291 [006] 1693209.504363: 1 branches:u: 7311943d6248 __libc_start_call_main+0x78 (/usr/lib/x86_64-linux-gnu/libc.so.6) => 5b4f1df51129 main+0x0 (/home/jiegec/test1)test1 3772291 [006] 1693209.504363: 1 branches:u: 5b4f1df51134 main+0xb (/home/jiegec/test1) => 5b4f1df5113a main+0x11 (/home/jiegec/test1)test1 3772291 [006] 1693209.504363: 1 branches:u: 5b4f1df5113e main+0x15 (/home/jiegec/test1) => 5b4f1df51136 main+0xd (/home/jiegec/test1)test1 3772291 [006] 1693209.504363: 1 branches:u: 5b4f1df5113e main+0x15 (/home/jiegec/test1) => 5b4f1df51136 main+0xd (/home/jiegec/test1)test1 3772291 [006] 1693209.504363: 1 branches:u: 5b4f1df5113e main+0x15 (/home/jiegec/test1) => 5b4f1df51136 main+0xd (/home/jiegec/test1)test1 3772291 [006] 1693209.504363: 1 branches:u: 5b4f1df5113e main+0x15 (/home/jiegec/test1) => 5b4f1df51136 main+0xd (/home/jiegec/test1)test1 3772291 [006] 1693209.504363: 1 branches:u: 5b4f1df5113e main+0x15 (/home/jiegec/test1) => 5b4f1df51136 main+0xd (/home/jiegec/test1)test1 3772291 [006] 1693209.504363: 1 branches:u: 5b4f1df5113e main+0x15 (/home/jiegec/test1) => 5b4f1df51136 main+0xd (/home/jiegec/test1)test1 3772291 [006] 1693209.504363: 1 branches:u: 5b4f1df5113e main+0x15 (/home/jiegec/test1) => 5b4f1df51136 main+0xd (/home/jiegec/test1)test1 3772291 [006] 1693209.504363: 1 branches:u: 5b4f1df5113e main+0x15 (/home/jiegec/test1) => 5b4f1df51136 main+0xd (/home/jiegec/test1)test1 3772291 [006] 1693209.504363: 1 branches:u: 5b4f1df5113e main+0x15 (/home/jiegec/test1) => 5b4f1df51136 main+0xd (/home/jiegec/test1)test1 3772291 [006] 1693209.504363: 1 branches:u: 5b4f1df5113e main+0x15 (/home/jiegec/test1) => 5b4f1df51136 main+0xd (/home/jiegec/test1)test1 3772291 [006] 1693209.504363: 1 branches:u: 5b4f1df51146 main+0x1d (/home/jiegec/test1) => 7311943d624a __libc_start_call_main+0x7a (/usr/lib/x86_64-linux-gnu/libc.so.6)

通过这个信息,就可以还原出程序执行了哪些代码:

  1. __libc_start_call_main 调用 main 函数,到入口 0x1129(main+0x0)
  2. 从 0x1134(main+0xb) 跳转到 0x113a(main+0x11)
  3. 循环 10 次:0x113e(main+0x15) 跳转到 0x1136(main+0xd)
  4. 最终 在 0x1146(main+0x1d) return 回到 __libc_start_call_main 函数

如果要看 Intel PT 到底生成了什么数据,可以用 perf script -D 显示。例如要记录分支跳转还是不跳转的历史,用的是如下的 TNT(Taken/Not Taken) packet:

TNT packet 的定义在 Intel® 64 and IA-32 Architectures Software Developer Manuals 中给出:

由于 Intel PT 的数据量很大,它和 SPE 类似,也是在内存中保存 trace 信息。

Intel LBR

Intel LBR(Last Branch Record) 机制记录了处理器最近若干次控制流转移,比如 taken branch。它记录的数量很小,信息直接保存在 MSR 当中,而不是像前面的 SPE 和 Intel PT 那样,需要在内存中记录信息。

LBR 在 perf 中,主要用来跟踪 call stack:设置 LBR,只记录 call 指令,并且打开 call-stack 模式,那么 LBR 记录的就是当前的 call stack,perf 可以利用这个信息来找到当前函数的调用链,虽然有长度限制。除了 lbr 以外,perf 还支持利用 fp(frame pointer) 或 dwarf(调试信息) 来寻找调用链

--call-graph Setup and enable call-graph (stack chain/backtrace) recording, implies -g. Default is "fp" (for user space). Valid options are "fp" (frame pointer), "dwarf" (DWARF's CFI - Call Frame Information) or "lbr" (Hardware Last Branch Record facility).

和 Intel PT 相比,它记录的信息较少,但实现上也更简单,开销更小。而且 LBR 只会记录跳转的分支,不会记录没跳转的分支,此外就是记录的分支数有上限。

Intel BTS

较早的 Intel 处理器没有实现 Intel PT,但考虑到 LBR 记录的历史长度限制,基于 LBR 做了一个把 LBR 信息保存到内存里的技术,叫做 Branch Trace Store。perf 也支持基于 BTS 来记录分支历史:

# recordsudo perf record --per-thread -e intel_bts//u ls# display tracesudo perf script# dump raw datasudo perf script -D

BTS 每个 entry 占用 24 字节,包括 8 字节的 Last Branch From 和 8 字节的 Last Branch To,还有 8 字节记录了分支预测正确还是错误(图源 Intel® 64 and IA-32 Architectures Software Developer Manuals):

和 LBR 一样,BTS 只记录跳转的分支,不记录没跳转的分支。

Intel PEBS

Intel PEBS(Processor Event Based Sampling) 是一种硬件的采样方法,顾名思义,当处理器触发某些事件时,自动进行一次采样。这个事件,实际上就是某个性能计数器溢出。本来,性能计数器溢出的时候,应该触发中断,让内核维护性能计数器的真实值(例如硬件实现了 32 位计数器,但内核维护的是 64 位);但在 PEBS 中,这个性能计数器的溢出事件被用来触发硬件的采样:计数溢出的时候,会捕捉当前的处理器状态(PC、访存地址和延迟、通用寄存器和浮点寄存器的值、时钟周期计数和 LBR 信息),把状态写入到内存中的缓冲区,并自动把性能计数器设为指定的复位值。当内存中的缓冲区满的时候,才会触发中断,让内核来处理 PEBS 生成的数据,并分配新的空间。

PEBS 可以精细地根据性能计数器来决定采样的频率,例如每 1000 条指令采样一次,每 1000 个周期采样一次,甚至每 1000 次缓存缺失采样一次。具体做法是,把对应的性能计数器的复位值设置为最大值减 1000,那么每次溢出触发 PEBS 采样以后,性能计数器会被设置为最大值减 1000,等性能计数器增加 1000 以后,再次溢出,触发 PEBS 采样,如此循环。

AMD 也有类似的机制,叫做 IBS(Instruction Based Sampling)。IBS 没有和 PMU 绑定起来,而是数指令数或数周期。推荐阅读论文 Precise Event Sampling on AMD Versus Intel: Quantitative and Qualitative Comparison,它深入比较了 AMD IBS 和 Intel PEBS 的差异。

如果要启用 PEBS 或 IBS,在 perf record 指令事件时,追加 :p

The p modifier can be used for specifying how precise the instruction address should be. The p modifier can be specifiedmultiple times: 0 - SAMPLE_IP can have arbitrary skid 1 - SAMPLE_IP must have constant skid 2 - SAMPLE_IP requested to have 0 skid 3 - SAMPLE_IP must have 0 skid, or uses randomization to avoid sample shadowing effects.For Intel systems precise event sampling is implemented with PEBS which supports up to precise-level 2, and precise level 3 for some special cases.On AMD systems it is implemented using IBS OP (up to precise-level 2).

详细信息见 perf-list(1) — Linux manual page

ARM BRBE

ARM 平台定义了 BRBE(Branch Record Buffer Extension),它和 Intel LBR 类似,也是在 System Register 中记录最近若干条跳转的分支的信息。它会记录 taken branch 的这些信息:

  1. 源地址
  2. 目的地址
  3. 距离上一次 taken branch 的周期数
  4. 分支类型
  5. 特权态(EL)
  6. 分支预测结果

不跳转的条件分支指令就不记录。和 LBR 类似,它也支持根据分支类型过滤,因此也可以用于 call graph 的抓取。

ARM SPE

ARM 平台定义了 SPE(Statistical Profiling Extension),它的做法是基于采样的:硬件上每过一段时间,采样一个操作,比如正在执行的指令;采样得到的操作的详细信息会写入到内存当中,由内核驱动准备好的一段空间。空间满的时候,会发中断通知内核并让内核重新分配空间。

perf record 在默认参数下的工作原理和 SPE 有点像,都是定时打断程序并采样:但 perf record 是在程序被打断后,由软件来收集信息;而 SPE 是由硬件采样,可以提供更多的微架构信息(延迟,访存地址,是否命中 TLB,分支跳转与否等等)。

SPE 的内核驱动实现在 arm_spe_pmu.c 当中;它做的事情是,在内存中分配好缓冲区,启动 SPE,并且在 SPE 触发中断时,进行缓冲区的维护;同时缓冲区中的数据会通过 perf ring buffer (aka perf aux) 传递给用户态的程序,具体数据的解析是由用户态的程序完成的。如果用 perf 工具,那么这个解析和展示的工作就是由 perf 完成的。

SPE 和 AMD IBS 类似,也是数指令数;和 Intel PEBS 不同,它没有和性能计数器耦合起来。

ARM AMU

ARM 除了提供性能计数单元(PMU)以外,还提供了 AMU(Activity Monitor Unit)。它和 PMU 很类似,也是有一些性能计数器,但 AMU 在计数器溢出的时候不会触发中断,所以它并不是拿来观察某个程序的性能怎么样,而是观察系统整体的状态,比如时钟频率,IPC 等等。

目前 AMU 的主要用途是在内核的调度器上:调度器需要估计一段时间内做了多少单位的任务,需要知道 CPU 的频率,比如频率高,就认为它做了更多的任务。之前的做法是让调度器通过 cpufreq 读取当前的 CPU 频率,但由于 CPU 的频率会不断变化,这样读取到的频率是一个瞬时功率,不能代表一段时间的平均值,就会带来误差。

为了获取一段时间内的平均频率,amu_scale_freq_tick 函数就使用了 AMU 的两个计数器:

  1. Processor Cycle 计数器:记录核心的周期数,也就是以 CPU 频率自增的计数器
  2. Constant Cycle 计数器:以固定频率(而非 CPU 频率)自增的计数器

通过记录一段时间内两个计数器的变化量,将两个变化量做除法,就可以得到一个正比于这段时间内 CPU 的平均频率的值,交给调度器。

参考

🔲 ☆

ARM Neoverse V2 (代号 Demeter) 微架构评测

ARM Neoverse V2 (代号 Demeter) 微架构评测

背景

ARM Neoverse V2 是目前(2024 年)在服务器上能用到的最新的 ARM 公版核平台(AWS Graviton 4),测试一下这个微架构在各个方面的表现。

官方信息

ARM 关于 Neoverse V2 微架构有如下公开信息:

考虑到 Neoverse V2 与 Cortex X3 的高度相似性,这里也列出 Cortex X3 的相关信息:

现有评测

网上已经有 Neoverse V2 微架构的评测和分析,建议阅读:

下面分各个模块分别记录官方提供的信息,以及实测的结果。读者可以对照已有的第三方评测理解。官方信息与实测结果一致的数据会加粗。

Benchmark

Neoverse V2 (AWS Graviton 4) 的性能测试结果见 SPEC

MOP vs uOP

MOP = Macro operation, uOP = Micro operation

ARM 公版核微架构既有 MOP 的概念,又有 uOP 的概念。uOP 主要是针对后端,执行单元处理的是 uOP。MOP 出现在 MOP Cache 以及 ROB 当中。他们和指令都并不是一一对应的关系。

例如 Instruction Fusion 特性,可以把多条指令合并到一条 uOP 当中,例如 CMP + CSET,合并成一个 uOP 以后,只需要一个 ALU 就可以完成整个操作。另一方面,一条指令也可能拆成多个 uOP,例如 128b Load Pair 指令,一条指令被拆成两个 uOP,可以独立执行,但为了保证精确异常,在 ROB 中还是同一个 MOP。

当然了,如果不考虑这些细节,大多数情况下,一条指令对应一个 MOP 对应一个 uOP 也是成立的。

前端

Branch Predictor

官方信息:Two predicted branches per cycle, nanoBTB + two level main BTB, 8 table 2 way TAGE direction predictor

L1 ICache

官方信息:64KB, 4-way set associative, VIPT behaving as PIPT, 64B cacheline, PLRU replacement policy

为了测试 L1 ICache 容量,构造一个具有巨大指令 footprint 的循环,由大量的 nop 和最后的分支指令组成。观察在不同 footprint 大小下的 IPC:

开始有一段 IPC 接近 12,此时指令由 MOP Cache 提供,由于连续的两条 NOP 可以被融合成一个 uOP,因此可以突破 8 的限制,但为什么是 12 还需要进一步研究。

当指令超出 MOP Cache 容量后,指令走 ICache + Decode,此时可以达到 6 的 IPC,与 6-wide 的 Decode Width 吻合。当 footprint 超出 64 KB 时,IPC 下降,对应了 64KB 的 L1 ICache 容量。

超出 L1 ICache 容量后,可以达到 4 的 IPC,说明 L2 Cache 可以提供每周期 16 字节的取指带宽。

MOP Cache

官方信息:1536 macro-operations, 4-way skewed associative, VIVT behaving as PIPT, NRU replacement policy, 8 MOP/cycle

因为 MOP Cache 的带宽比 Decode 高,为了测试出 MOP Cache 的规格,需要构造指令序列,使其可以达到 8 MOP/cycle 的 IPC,如果走的是 Instruction Fetch + Decode,则达不到这个 IPC。但是 Neoverse V2 的 Dispatch 有比较明确的限制:

The dispatch stage can process up to 8 MOPs per cycle and dispatch up to 16 µOPs per cycle, with the following limitations on the number of µOPs of each type that may be simultaneously dispatched.

  • Up to 4 µOPs utilizing the S(单周期整数)or B(分支)pipelines
  • Up to 4 µOPs utilizing the M(多周期整数)pipelines
  • Up to 2 µOPs utilizing the M0(多周期整数)pipelines
  • Up to 2 µOPs utilizing the V0(浮点/向量)pipeline
  • Up to 2 µOPs utilizing the V1(浮点/向量)pipeline
  • Up to 6 µOPs utilizing the L(访存)pipelines

考虑到这个限制,使用 4 条 add 指令,4 条 fadd 指令为一组,不断重复。通过测试,这样的指令序列确实可以达到 8 的 IPC。当指令个数增加到超出 MOP Cache 容量时,将会观察到性能的下降:

拐点出现在 192 个指令组,此时达到 MOP Cache 的容量瓶颈,192*8=1536,正好是 MOP Cache 的容量。

L1 ITLB

官方信息:Caches entries at the 4KB, 16KB, 64KB, or 2MB granularity, Fully associative, 48 entries

构造一系列的 B 指令,使得 B 指令分布在不同的 page 上,使得 ITLB 成为瓶颈:

可以看到 48 Page 出现了明显的拐点,对应的就是 48 的 L1 ITLB 容量。此后性能降低到 7 CPI,此时对应了 L2 Unified TLB 的延迟。

进一步增加 Page 数量,发现在大约 1000 个页的时候,时间从 7 cycle 逐渐上升:

考虑到 L2 Unified TLB 一共有 2048 个 Entry,猜测它限制了 ITLB 能使用的 L2 TLB 的容量只有 2048 的一半,也就是 1024 项。超出 1024 项以后,需要 Page Table Walker 进行地址翻译。

Decode

官方信息:6-wide Decode

Return Stack

Return Stack 记录了最近的函数调用链,call 时压栈,return 时弹栈,从而实现 return 指令的目的地址的预测。构造不同深度的调用链,发现 Neoverse V2 的 Return Stack 深度为 32:

后端

Dispatch

官方信息:up to 8 MOPs per cycle and up to 16 uOPs per cycle

Store to Load Forwarding

官方信息:

The Neoverse V2 core allows data to be forwarded from store instructions to a load instruction with the restrictions mentioned below:

  • Load start address should align with the start or middle address of the older store
  • Loads of size greater than or equal to 8 bytes can get the data forwarded from a maximum of 2 stores. If there are 2 stores, then each store should forward to either first or second half of the load
  • Loads of size less than or equal to 4 bytes can get their data forwarded from only 1 store

经过实际测试,如下的情况可以成功转发:

对地址 x 的 Store 转发到对地址 y 的 Load 成功时 y-x 的取值范围:

Store\Load8b Load16b Load32b Load64b Load
8b Store{0}{}{}{}
16b Store{0,1}{0}{}{}
32b Store{0,2}{0,2}{0}{-4,0}
64b Store{0,4}{0,4}{0,4}{-4,0,4}

一个 Load 需要转发两个 Store 的数据的情况:对地址 x 的 32b Store 和对地址 x+4 的 32b Store 转发到对地址 y 的 64b Load,在 Overlap 的情况下,要求 y=x,也就是恰好前半来自第一个 Store,后半来自第二个 Store。

和官方的描述是比较符合的,只考虑了全部转发、转发前半和转发后半的三种场景。特别地,针对常见的 64b Load,支持 y-x=-4。同时也支持前半和后半来自两个不同的 Store。对地址本身的对齐没有要求,甚至在跨缓存行边界时也可以转发,只是对 Load 和 Store 的相对位置有要求。

Zen 5 相比,Neoverse V2 对 Store 和 Load 的相对位置有额外的要求(开头或正中央),但支持了 Store 和 Load 只有一部分覆盖的情况,也允许一个 Load 从两个 Store 中取得数据。

从性能上,可以转发时 5 Cycle,有 Overlap 但无法转发时 10.5 Cycle。

小结:ARM Neoverse V2 的 Store to Load Forwarding:

  • 1 ld + 1 st: 要求 ld 和 st 地址相同或差出半个 st 宽度
  • 1 ld + 2 st: 要求 ld 和 st 地址相同
  • 1 ld + 4 st: 不支持

计算单元

官方信息:6x ALU, 2x Branch, 4x 128b SIMD

实测以下指令的吞吐:

  • int add: 4 IPC,受到 Dispatch 限制:Up to 4 µOPs utilizing the S(单周期整数)or B(分支)pipelines
  • int mul: 2 IPC,对应两个 Multi Cycle 单元
  • int not taken branch: 2 IPC,对应两个 Branch 单元
  • asimd fadd double: 4 IPC,对应四个 FP/ASIMD 单元

Load Store Unit

官方信息:2 Load/Store Pipe + 1 Load Pipe, Reduce bandwidth or incur additional latency for:

  1. Load operations that cross a cache-line (64-byte) boundary.
  2. Quad-word load operations that are not 4B aligned.
  3. Store operations that cross a 32B boundary.

经过测试,一个周期内可以最多完成如下的 Load/Store 指令:

  • 3x 64b Load
  • 2x 64b Load + 1x 64b Store
  • 1x 64b Load + 2x 64b Store
  • 2x 64b Store

这个性能符合 2 LS + 1 LD pipe 的设计。

经过测试,当 Load 指令没有跨越缓存行时,load to use 延迟是 4 cycle;当 Load 指令跨过 64B 缓存行边界时,load to use 延迟增加到 5 cycle。

Memory Dependency Predictor

为了预测执行 Load,需要保证 Load 和之前的 Store 访问的内存没有 Overlap,那么就需要有一个预测器来预测 Load 和 Store 之前在内存上的依赖。参考 Store-to-Load Forwarding and Memory Disambiguation in x86 Processors 的方法,构造两个指令模式,分别在地址和数据上有依赖:

  • 数据依赖,地址无依赖:str x3, [x1]ldr x3, [x2]
  • 地址依赖,数据无依赖:str x2, [x1]ldr x1, [x2]

初始化时,x1x2 指向同一个地址,重复如上的指令模式,观察到多少条 ldr 指令时会出现性能下降:

有意思的是,地址依赖的阈值是 40,而数据依赖没有阈值。

Move Elimination

官方信息:特定情况下这些指令可以被优化:mov reg, 0; mov reg, zero; mov vreg, 0; mov reg, reg;mov vreg, vreg

实际测试,各种模式的 IPC 如下:

  • mov reg, 0: IPC 6
  • mov vreg, 0: IPC 6
  • mov reg, reg: 无依赖链时 IPC 4
  • mov vreg, vreg: 无依赖链时 IPC 3.6

虽然做了优化,但算不上很快。

Reorder Buffer

官方信息:320 MOP ROB, 8-wide retire

把两个串行的 fsqrt 序列放在循环的头和尾,中间用 NOP 填充,如果 ROB 足够大,可以在执行开头串行的 fsqrt 序列时,同时执行结尾串行的 fsqrt 序列,此时性能是最优的。如果 ROB 不够大,那么会观察到性能下降。由于 Neoverse V2 执行 NOP 可以达到接近 12 的 IPC,所以只需要很少的 fsqrt 就足够生成足够的延迟。

通过测试,发现在大约 640 条 NOP 时出现性能下降,而 Neoverse V2 实现了 Instruction Fusion,两条 NOP 指令算做一条 uOP,同时也是一条 MOP,因此 640 条 NOP 对应 320 MOP 的 ROB 大小。极限情况下,320 MOP 可以存 640 uOP,但是实际上比较难达到,很容易受限于其他结构。

L1 DCache

官方信息:64KB, 4-way set associative, VIPT behaving as PIPT, 64B cacheline, ECC protected, RRIP replacement policy, 4×64-bit read paths and 4×64-bit write paths for the integer execute pipeline, 3×128-bit read paths and 2×128-bit write paths for the vector execute pipeline

容量

构造不同大小 footprint 的 pointer chasing 链,测试不同 footprint 下每条 load 指令耗费的时间:

可以看到 64KB 出现了明显的拐点,对应的就是 64KB 的 L1 DCache 容量。之后延迟先上升后下降,与 ARM 采用的 Correlated Miss Caching(CMC) 预取器记住了 pointer chasing 的历史有关,详细可以阅读 Arm Neoverse N2: Arm’s 2nd generation high performance infrastructure CPUs and system IPs

延迟

经过测试,L1 DCache 的 load to use latency 是 4 cycle,没有针对 pointer chasing 做 3 cycle 的优化。

吞吐

使用 FP/ASIMD 128b Load 可以达到 3 IPC,对应了 3x128b read paths;而如果使用 2x64b 整数 LDP,则只能达到 2 IPC,对应 4x64b read paths。也就是说,要达到峰值的读取性能,必须用 FP/ASIMD 指令。写入方面,向量 128b Store 可以达到 2 IPC,对应了 2x128b write paths;类似地,2x64b 整数 STP 能达到 2 IPC,对应 4x64b write paths。

VIPT

在 4KB page 的情况下,64KB 4-way 的 L1 DCache 不满足 VIPT 的 Index 全在页内偏移的条件(详见 VIPT 与缓存大小和页表大小的关系),此时要么改用 PIPT,要么在 VIPT 的基础上处理 alias 的问题。为了测试这一点,参考 浅谈现代处理器实现超大 L1 Cache 的方式 的测试方法,用 shm 构造出两个 4KB 虚拟页映射到同一个物理页的情况,然后在两个虚拟页之间 copy,发现相比在同一个虚拟页内 copy 有显著的性能下降,并且产生了大量的 L1 DCache Refill:

copy from aliased page = 3261121467 cycles, 285103870 refillsbaseline = 1520692165 cycles, 1200 refillsslowdown = 2.14x

因此验证了 L1 DCache 采用的是 VIPT,并做了针对 alias 的正确性处理。如果是 PIPT,那么 L1 DCache 会发现这两个页对应的是相同的物理地址,性能不会下降,也不需要频繁的 refill。

构造

进一步尝试研究 Neoverse V2 的 L1 DCache 的构造,为了支持每周期 3 条 Load 指令,L1 DCache 通常会分 Bank,每个 Bank 都有自己的读口。如果 Load 分布到不同的 Bank 上,各 Bank 可以同时读取,获得更高的性能;如果 Load 命中相同的 Bank,但是访问的 Bank 内地址不同,就只能等到下一个周期再读取。为了测试 Bank 的构造,设计一系列以不同的固定 stride 间隔的 Load 指令,观察 Load 的 IPC:

  • Stride=1B/2B/4B/8B/16B/32B: IPC=3
  • Stride=64B: IPC=2
  • Stride=128B/256B/512B: IPC=1

Stride=64B 时出现性能下降,说明此时出现了 Bank Conflict,进一步到 Stride=128B 时,只能达到 1 的 IPC,说明此时所有的 Load 都命中了同一个 Bank,并且是串行读取。根据这个现象,认为 Neoverse V2 的 L1 DCache 组织方式和限制是:

  • 一共有两个 Bank,Bank Index 是 VA[6]
  • 每个 Bank 每周期可以从一个缓存行读取数据
  • 支持多个 Load 访问同一个缓存行
  • 如果多个 Load 访问同一个 Bank 的不同缓存行,只能一个周期完成一个 Load

这里讨论的是缓存行级别的 Bank,实际上通常缓存行内部也会进行 Bank 划分,但主要是为了功耗,比如从一个 64B 缓存行里读取 8B 数据,不需要把整个 64B 都读出来。

L1 DTLB

官方信息:Caches entries at the 4KB, 16KB, 64KB, 2MB or 512MB granularity, Fully associative, 48 entries. A miss in the L1 data TLB and a hit in the L2 TLB has a 6-cycle penalty compared to a hit in the L1 data TLB.

用 pointer chasing 的方法测试 L1 DTLB 容量,指针分布在不同的 page 上,使得 DTLB 成为瓶颈:

可以看到 48 Page 出现了明显的拐点,对应的就是 48 的 L1 DTLB 容量。超出容量后,需要额外的 5 cycle 的 latency 访问 L2 Unified TLB。

L2 Unified TLB

官方信息:Shared by instructions and data, 8-way set associative, 2048 entries

L2 Cache

官方信息:1MB or 2MB, 8-way set associative, 4 banks, PIPT, ECC protected, 64B cacheline, 10 cycle load-to-use, 128 B/cycle

SVE

官方信息:128b SVE vector length

在 Linux 下查看 /proc/sys/abi/sve_default_vector_length 的内容,得到 SVE 宽度为 16 字节,也就是 128b。

实测发现 Neoverse V2 每周期最多可以执行 4 条 ASIMD 或 SVE 的浮点 FMA 指令,也就是说,每周期浮点峰值性能:

  • 单精度:128/32*2*4=32 FLOP per cycle
  • 双精度:128/64*2*4=16 FLOP per cycle

与 Zen 2-4、Oryon、Firestorm、LA464、Haswell 等微架构看齐,但不及 Zen 5、Skylake 等通过 AVX512 提供的峰值浮点性能。

🔲 ☆

Linux ARM生态评测

看看现在的Linux ARM能不能替代macOS?

起因

我的树莓派4B从好久之前就一直吃灰了,之前用它装过UbuntuopenFydeWindows 11piCore,但都因为性能和使用体验不佳放弃使用了。不过随着华为的某系统发布以及高通出的某个笔记本电脑用处理器,我对运行在ARM指令集CPU系统的生态产生了一些兴趣。macOS的生态之前我已经体验过了,是符合预期的不错。Windows on ARM虽然在树莓派上装了试着用了但是没驱动太卡了,其实没有体现它怎么样,要想体验还得整个高通CPU的拿来试,不过我手头没有所以没办法😂,那在树莓派上的Linux系统我也试过不少,有什么测试的必要吗?其实还是有的,因为之前我测都是当服务器测的,虽然也测了openFyde(ChromeOS),但是生态其实挺垃圾的,虽然能用Linux软件但是因为是容器卡的不能用。所以这次我想装树莓派官方的Raspberry Pi OS完整版来测测现在Linux ARM生态能不能和我现在用的macOS比拼。
另外前段时间树莓派出了新的连接方式:Raspberry Pi Connect,可以登录树莓派官网的账号然后用浏览器操作图形界面或者命令行,可以自动判断使用P2P模式还是中继模式,而且可以根据浏览器界面大小自动修改树莓派的分辨率,体验还不错。

与我Mac上软件的替代测试

原生应用测试

既然是和macOS相比,那就看看我现在用的软件是不是能在树莓派上原生运行吧。首先是常用的国产软件,比如WPS Office,钉钉,微信,QQ。因为UOS的缘故,大多数常用的国产软件都有Linux ARM的版本,首先钉钉和QQ在官网上可以直接下载deb包安装,运行也没什么问题,功能完全一致,而且也没有卡到不能用的程度,对于树莓派来说已经很让我满意了。WPS Office和微信稍微有点麻烦,官网并没有提供安装包,但是我找到一个不错的国产Linux应用商店——星火应用商店。里面有不少Debian系各种CPU架构的国产软件,官网上没有的也能在这里下到,让我很满意。不过里面有好多Wine的应用……我不是特别想用,而且不知道它是怎么处理的,会不会一个软件安装一个Wine?所以就先不考虑了。随后我在里面找到了WPS Office和微信,安装试了一下,微信看起来还不错,至少小程序,视频号之类的都能用(反正是基于浏览器的,好适配🤣),WPS Office虽然能用,但是刚安装是英文版的……而且中文切换找了半天找不到😅,后来找了半天才找到……不过安了WPS Office,应该再配个中文输入法才对,我试着安装了搜狗输入法,但是安装好之后不能用,Fcitx不停崩溃重启,不知道是什么问题,换了谷歌输入法之后就正常了。
除了常用的国产软件之外,还有一些我平时开发用的软件,这些软件对Linux ARM的支持也挺不错的,可能国外也是比较支持Linux ARM生态吧(大概是因为Chromebook?)。我平时用的VSCode当然是有的,不过数据库管理和接口调试我在Mac用的是Sequel Ace和RapidAPI,这两个是专为macOS设计的,当然没有Linux版。但是这些是有替代品的,我找了一下,数据库我用的是Navicat Premium Lite,它有原生Linux ARM版,但是是AppImage……感觉不是很舒服。接口调试的话用的是Apipost,估计就是因为用的Electron的所以才愿意整跨平台的版本吧。Mac上有时候我还会远程桌面到Windows主机,这个树莓派也可以,有个叫Remmina的客户端可以用,效果也还不错,如果不是局域网连接还有RustDesk可以用(虽然不知道为什么中文会变方块😂)。另外还有用来测试的网站环境,这个倒是比macOS更简单,毕竟Linux有那么多面板,也不需要敲命令安装,而且还可以运行Docker,我这次用的是1Panel,使用基本上没什么问题,还能安装杀毒软件😁(虽然MongoDB安装会因为缺少指令集报错用不了,但是我用不着🤣)。
除此之外还有虚拟机,这个在之前Ubuntu Server上已经测过了,不过那时候是无头模式,现在可以在图形界面用virt-manager来管理了,之前安装了Windows,这次就安装个FreeBSD吧,安装起来也不复杂,和其他虚拟机管理软件一样,而且还能用虚拟串口连接,感觉还挺有意思的。安装好之后上网之类的都没问题,和在macOS上用UTM的区别可能就只有在macOS上可以把Rosetta 2穿透到Linux下使用吧。
另外还有游戏,专门为Linux ARM设计的游戏估计没几个,不过想玩肯定是有的,比如用Ren’Py引擎的游戏以及在浏览器上的游戏,其他引擎似乎没什么资料……但没事,在macOS上也是用的iOS版的模拟器,后面讲到的安卓也可以运行模拟器😁。我之前也研究过在macOS上玩Ren’Py引擎的游戏。不过Ren’Py默认发行是不支持Linux ARM版的……但是可以另外下载SDK来支持。然而有一个问题,只有新版的SDK才支持64位的ARM,旧版虽然有树莓派支持,但可能是因为旧版树莓派只有32位的系统所以没有64位ARM的运行库😂。我看了看我电脑上之前下的Ren’Py引擎的游戏,找了一个《Sakura Isekai Adventure》游戏看了一下Ren’Py的版本还可以,SDK也能正常的在树莓派上运行,试了一下感觉效果还不错,运行的方法是“SDK所在目录/renpy.sh 游戏所在目录/Game”,之前没加Game不停报错😅,文档写的也不清晰,测了半天才测出来……那对于旧版的就不能玩了吗?估计是可以但可能要自己编译很麻烦,反正源代码都有。不过有个例外,我本来想试试《Katawa Shoujo》,它用的Ren’Py很旧,但是因为是同人类游戏所以有人做了重制版《Katawa Shoujo: Re-Engineered》😆,这个是用的最新版的Ren’Py,增加了新的特性和各种BUG,但是正因如此,可以简单的在树莓派上运行了🤣。
至于其他关于AI方面的比如LLaMA和Stable Diffusion,这些毕竟是开源的,Linux ARM当然可以运行,只不过树莓派的GPU不能用来加速,运行会很卡而已,生态方面是没问题。

安卓软件测试

既然macOS可以原生运行iOS软件,那对于Linux来说自然应该对比一下原生运行安卓软件了。关于安卓软件我之前在Ubuntu Server上已经测了Waydroid和redroid。但毕竟当时是在无头模式下测的,没有图形界面,现在有了图形界面可以再测一下。安装除了要挂梯子下载镜像之外没什么问题,但是打开的时候不知道为什么只会黑屏……后来搜了一下,执行“waydroid prop set persist.waydroid.multi_windows true”再重启就没问题了。虽然安卓软件比iOS的要更多,不过毕竟树莓派的性能想玩手游还是有点勉强,当然这次测的是生态,所以还是完全没问题😁。

转译应用测试

既然macOS有Rosetta 2可以运行x86架构的软件,那Linux ARM当然也不能少,这个方案比较多,有QEMU,Box86/64还有ExaGear,不过听说ExaGear性能相对更好一些,那这次就测这个吧。
现在ExaGear已经被华为收购了,想要下载的话在华为源里就能下到,我装的是4.0.0的,因为4.1.0似乎要自己配置镜像太麻烦了所以就没用。安装很简单,直接把对应目录的deb包安装了就可以,安装好之后就可以执行“exagear”进到转译后的Bash中,不过和macOS有个区别,macOS的程序都是通用二进制文件,里面包含了ARM架构和x86架构的程序,所以可以无缝衔接,Linux当然没有这个特性,所以ExaGear是映射到它自己的镜像里面的,各种包还得另外安装。
那这个东西装什么比较好呢?我发现我的Mac上有个网易云音乐,在Linux上似乎没有ARM版的,在星火应用商店也只有Wine版。但是它之前和深度合作出过Linux版,现在估计谈崩了从官网上消失了,但是原来的链接还在可以下载。具体的流程在CSDN上有篇博客有写,试了一下可以安装,而且运行效率比我预期高不少,最起码点击不算卡,而且听音乐也没有卡顿的感觉,感觉算是相当不错了。
其实我也挺疑惑Rosetta 2和ExaGear的效率怎么样,我看网上有篇文章Comparing Huawei ExaGear to Apple’s Rosetta 2 and Microsoft’s solution说ExaGear效率最高,Rosetta 2有硬件加速都比不上,说实话我是不信的,要是那么厉害Eltechs怎么可能停更?而且全网就这一篇文章,很难不相信是华为员工写的软文😅,我自己手头没有合适的设备也不好测,不知道有没有大佬来打假。
那运行转译的Linux软件没问题之后再测一下转译Windows应用吧,我的Mac上可是有Whisky的。那对树莓派来说就是ExaGear+Wine了。安装很简单,直接在ExaGear的shell里用apt安装就行,安装好之后就可以用“exagear – wine ./windows程序.exe”来运行了。我在我的Mac上找了一个用Godot引擎写的小游戏,放上去试了一下,居然可以运行,而且也是比想象中的流畅,不过我玩的本来就是画面变动少的游戏也不会卡到哪里,不过能在接受范围内有反应已经很不错了,虽然没Mac反应快但毕竟测生态和芯片本身速度无关,树莓派的性能当然比不了Mac,能玩我已经很满足了。
其实如果论游戏的话在x86平台毕竟有SteamOS的先例,用ExaGear转译然后加上Proton如果芯片性能足够的情况应该是能玩不少游戏的。

其他实验

在玩树莓派的时候我又想玩电台了🤣毕竟这是树莓派唯一的优势,能用到它的GPIO接口,不然真的就是性价比不怎么样,性能还差的ARM迷你主机了。这次我多试了一下怎么样把图形界面上的声音通过广播传出来,如果可以的话树莓派离得比较远而且不用蓝牙耳机的情况下也能听到声音了。不过我不太清楚Linux中的声音是怎么合成的,我搜了一下似乎是用PulseAudio合成的,用“pactl list sources”命令就可以列出相关的设备,在我的树莓派上可以看到是叫“alsa_output.platform-bcm2835_audio.stereo-fallback.monitor”,然后用

sox -t pulseaudio alsa_output.platform-bcm2835_audio.stereo-fallback.monitor -t wav - | sudo ./pi_fm_adv --audio - --freq 87.0 --power 7 --gpio 4 --gpio 20 --gpio 32 --rds 0

命令理论上就可以发射电台了,但实际上不知道为什么虽然能听到声音,但是声调变的很高,而且一卡一卡的,根本不能听,而且进程会卡住,要用kill -9才能结束😓……
不过这个就和Linux ARM生态无关了,这是只有树莓派才有的特殊功能,其他电脑估计做不到这一点😆。

感想

这次测下来感觉Linux ARM好像还挺强的啊,基本上我Mac上装的东西都有,而且功能也挺齐全,从原生应用的角度来看可能比Windows on ARM还多。看来除了易用性之外Linux ARM生态已经很成熟了啊,这么来看Mac就只剩下美观、易用性和芯片性能强大这些优势了啊😂。

🔲 ☆

DaRM - 基于 Go 的静态博客生成器

这个项目是我的毕业论文,经常看我博客的朋友会发现,我的站点已经脱离了 WordPress,换成了 DaRM。上个月缝缝补补,将它开源了出来,并且将自己的博客系统切换到了这个程序,目前体验良好。虽说功能是很简陋,但主要还是贴合我的需求,有兴趣的朋友可以玩玩。


DaRM

项目地址:DaRM

基于 Go 语言轻量、快速、易使用的开源博客系统。

文档地址:DaRM Docs

特性

专注于文字

DaRM 系统专注于提供文本内容的创建和管理,尤其适合博客文章和文档的撰写与发布。

原生支持 Markdown 格式,允许用户以简洁的语法快速撰写格式化文本。

易于使用

部署简单,支持不同平台的多种部署方式。

提供了一个用户友好的界面和直观的操作流程,使得即使是初学者也能轻松上手。

性能优秀

基于 Go 语言开发,快速且性能优秀!

静态文件同步

支持将静态文件同步至 GitHub,或通过 FTP 协议同步至服务器。

部署 DaRM

目前支持三种部署方式,编译部署、手动部署、Docker 部署。

编译部署

环境要求

  • git
  • Go 版本 >=1.22.0

克隆仓库

1
git clone https://github.com/bitaur/darm.git

构建程序

1
go build ./

手动部署

下载 DaRM

打开 DaRM Release 下载对应的平台以及系统的文件。

如果最新的包没有您对应的二进制文件,可以提交 issues ,或可以选择自己编译安装,详见:编译安装

其中:

armv6 对应 arm 架构32位版本,arm64 对应 arm 架构64位版本。

x86 对应 x86 平台32位版本,x86_64 对应 x86 平台64位版本。

手动运行

Linux / MacOS

1
2
3
4
5
6
7
# 解压下载后的文件,请求改为您下载的文件名
tar -zxvf filename.tar.gz

# 授予执行权限
chmod +x DaRM

./DaRM

Windows

双击运行即可。

持久化运行

Linux

使用编辑器编辑 /usr/lib/systemd/system/darm.service 添加如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
[Unit]
Description=darm
After=network.target

[Service]
Type=simple
WorkingDirectory=darm_path
ExecStart=darm_path/darm
Restart=on-failure

[Install]
WantedBy=multi-user.target

保存后,使用 systemctl deamon-reload 重载配置。具体使用命令如下:

  • 启动: systemctl start darm
  • 关闭: systemctl stop darm
  • 配置开机自启: systemctl enable darm
  • 取消开机自启: systemctl disable darm
  • 状态: systemctl status darm
  • 重启: systemctl restart darm

更新版本

如果有新版本更新,下载新版本,将旧版本的文件删除即可。

Docker 部署

首先请确保您正确的安装并配置了 Docker 以及 Docker Compose

Docker CLI

1
docker run -d --restart=unless-stopped -v /data/darm:/data -p 9740:9740 --name="DaRM" bitaur/darm:latest

Docker Compose

在空目录中创建 docker-compose.yaml 文件,将下列内容保存。

1
2
3
4
5
6
7
8
9
services:
DaRM:
image: bitaur/darm:latest
container_name: DaRM
volumes:
- /data/darm:/data
restart: unless-stopped
ports:
- 9740:9740

保存后,使用 docker compose up -d 创建并启动容器。

Docker 容器更新

CLI

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#查看容器ID
docker ps -a

#停止容器
docker stop ID

#删除容器
docker rm ID

#获取新镜像
docker pull bitaur/darm:latest

# 输入安装命令
docker run -d --restart=unless-stopped -v /data/darm:/data -p 9740:9740 --name="DaRM" bitaur/darm:latest

Docker Compose

1
2
3
4
5
#获取新镜像
docker pull bitaur/darm:latest

#创建并启动容器
docker compose up -d

访问

此时打开 localhost:9740 即可打开站点。默认账号密码均为 admin

后语

最后其实也想说一下,个人水平很差,只是勉强能跑,这个项目的主要需求还是能够让我顺利毕业。很多Bug 还是来不及去修。如果后期有啥问题可以直接留言。

🔲 ☆

三星 Exynos CPU 微架构学习笔记

三星 Exynos CPU 微架构学习笔记

背景

ISCA 2020 的一篇文章 Evolution of the Samsung Exynos CPU Microarchitecture 非常详细地解析了三星 Exynos 自研 CPU 微架构的演进历史。本文是对这篇论文的学习和整理的笔记。

分支预测器

文章 Chapter IV 讲述了 Exynos 系列微架构的分支预测器实现。Exynos 微架构用的是 Scaled Hashed Perceptron 分支方向预测器,这个分支预测器的提出者 Daniel A. Jiménez 也在这篇论文的作者列表中。现在采用基于 Perceptron 的分支预测器的处理器不多,AMD 的 Zen 1 用了,Zen 2 是 Perceptron 加 TAGE,Zen 3 以后就只有 TAGE 了。

除了方向预测器,还需要有 BTB 来识别分支以及记录分支的目的地址。为了性能,每个周期都要预测至少一个分支,所以一般会有一个 0-bubble 的 BTB,在这里叫 uBTB(microBTB,用 u 代替希腊字母 μ)。但也因为时序的限制,不会做的太大。为了支持有更大的容量,通常还会有更大的,延迟也更长的 BTB,在这里叫 mBTB(Main BTB),最大的是 L2 BTB,还有后面会讲到处理边界情况的 vBTB。同理,分支预测器也有容量和延迟的双重考虑,设置不同大小和容量的分支预测器,也可能会分多级,和 0-bubble uBTB 配对的 LHP(Local History Perception)以及 1-2 bubble mBTB 配对的完整的 SHP(Scaled Hashed Perception)。此外还有 RAS(Return Address Stack)负责函数返回地址的预测。

首先从 Exynos 最早的分支预测器设计开始。一开始设计的时候,就考虑到要支持 2 prediction/clock 的场景,前提是第一个分支是 not taken 的。例如有两条分支指令,第一条是条件分支指令,如果第一条分支 taken,那就以第一条分支的结果为准;如果第一条分支 not taken,那就应该以第二条分支的结果为准。这样可以在比较低的开销的前提下,一个周期预测两条分支指令,提高分支预测的性能,如果不做这个优化的话,需要先预测第一条分支,预测完,再去预测第二条分支。论文中指出,对于这种需要预测 2 个分支的场景,有 60% 的情况是第一条分支 taken,24% 情况是第一条分支 not taken 并且第二条分支 taken,两个都 not taken 的情况占 16%。那么后 40% 的情况就可以得到性能提升。这个优化还是挺常见的,例如香山南湖架构做了这个优化,而且虽然可以 2 predictions/cycle,但实际上只有一个 Fetch Packet,也最多 1 taken prediction/cycle。

SHP,也就是那个最大的 Perceptron 预测器,包含了 8 个表,每个表有 1024 个权重,每个 BTB 表项还给每个分支记录了一个 bias。预测的时候,根据 GHR(根据分支 taken/not taken 历史)和 PHR(根据 taken branch 地址)以及分支的 PC 经过哈希,得到 table 的 index,根据 index 去访问权重。把从 8 个表里读出来的权重加起来,再加上 bias 的两倍,得到最早的计算结果,如果是非负数,则预测为 taken;负数则预测为 not taken。

知道分支的实际跳转方向后,如果预测错误了,或者预测对了,但是计算结果太接近 0,就需要更新 weight。更新时,会更新参与到计算的来自各个表的 weight,如果是 taken,那就增加 weight;如果是 not taken,就减少 weight。Exynos M1 采用了 165 位的 GHR,也就是最近 165 条条件分支的跳转方向,以及 80 位的 PHR,每个 taken branch 会向 PHR 贡献 3 个 bit(B[4:2]),但是没说移位多少。这样就构成了一个 Perceptron 预测器。

特别地,针对总是跳转的分支,例如无条件跳转分支,或者总是跳转的条件分支,就不用更新 SHP 了,他们只需要在 BTB 中标记一下即可,避免污染 SHP,干扰其他分支的预测。类似的思路也挺常见的,AMD 的做法是,对于从来没有跳转过的条件分支指令,不分配 BTB 表项,并且预测为不跳转;当条件分支了跳转第一次,那就会在 BTB 中分配表项,标记为 always taken,表示预测为总是跳转;当条件分支跳转和不跳转各至少一次,才启用分支方向预测器。

接下来是 BTB。mBTB,也就是上面提到的有 1-2 bubble 的略微大一些的 BTB,可以给 128B 大小的 cacheline 保存 8 条分支指令。统计数据表示,平均每 5 条指令有一条是分支指令,那么 128B 在 4 字节定长指令的情况下,可以存 32 条指令,估算得到大约有 6-7 条分支指令,所以设计了可以存 8 条。但也有可能分支密度很高,128B 全是分支指令,那就会有 32 条分支指令了。

为了解决这个问题,设计了 vBTB(virtual indexed BTB),可以把 128B 的 cacheline 里超过 8 个 branch 的多余部分保存下来,当然了,会有额外的开销。

比较特别的是,没有设计单独的例如 ITTAGE 那样的 Indirect Predictor,而是采用了叫做 VPC(Virtual PC)的方案,它的思路是,复用方向预测器和 BTB 的能力,把一条 Indirect Branch 映射为多条 Conditional Branch,每个 Conditional Branch 的 Target 对应一个可能的 Indirect Branch Target,这个 Target 就存在 BTB 当中。预测的时候,按照顺序遍历每个虚拟的 Conditional Branch,如果预测为 taken,那就跳转到这个虚拟的分支的目的地址;如果预测为 not taken,那就遍历到下一个虚拟的分支。如果所有的虚拟条件分支都被预测为不跳转,那就需要等到后端计算出实际的目的地址,再跳转。对于每个 Indirect Branch,这样的虚拟条件分支最多生成 16 个。

这些虚拟的条件分支既然要利用 BTB 的空间,自然也会抢占 128B cacheline 最多 8 条分支的限制,多余的分支或者 Indirect Branch 生成的虚拟条件分支也会溢出到 vBTB 内。这种设计还是第一次见。

由于这个初始的分支预测器设计只有一个 main BTB,需要 2 bubble 才能出一个 taken branch,在 taken branch 很密集的时候,就需要三个周期一个 taken branch 了,这性能肯定不够好。所以 Exynos M1 引入了 0-bubble 的 uBTB,容量比较小,好处是快,一般这种 0-bubble BTB 也可以叫做 Next Line Predictor,预测下一个周期的 Fetch 地址。

为了实现 0-bubble 的 uBTB,Exynos M1 用了一个基于图的结构来记录分支之间的跳转关系,配合一个记录分支局部历史的 Hashed Perceptron 算法来预测方向。这个预测算法也是第一次见,在三星的专利 High performance zero bubble conditional branch prediction using micro branch target buffer 中提出,大题思路其实就是把基本块学习出来,找到分支之间 taken 和 not taken 的关系,以每个分支为一个结点,如果一个分支 taken 以后会到另一个分支,那就在这两个分支对应的结点之间连一条 taken 的有向边,类似地,not taken 也会连 not taken 的有向边。

有了图以后,就可以直接从图中知道下一个会到达的分支在哪里,即使这个分支可能距离很远;而常规的 BTB 设计里,则是拿到地址以后,用地址去寻找匹配的 BTB entry,这个过程中可能会扫描到一些不存在分支指令的代码块。这里也支持前面说的连续两个分支同时预测的情况,在同一个周期内预测两个分支,如果第一个分支不跳,就用第二个分支的结果,在树上转移。为了省电,当 uBTB 预测准确率较高,图记录了最近执行的所有分支,那么 uBTB 就可以火力全开,保持 0-bubble 的预测,这个预测在后面的流水线中会被 mBTB 和 SHP 进行进一步的确认。如果准确率特别高,认为 mBTB 和 SHP 大概率也会得到相同的预测结果,就会进一步停止 mBTB 和 SHP 的使用,降低功耗,这时候就要靠后端的 Branch Unit 来检查预测是否正确。

这就是 Exynos M1 和 M2 的分支预测器设计。上面的专利High performance zero bubble conditional branch prediction using micro branch target buffer还给出了前端的流水线各级的功能:

这个图中没有绘制 uBTB 内部的结构,uBTB 负责给出初始的预测,到 B1 阶段,从 B1 开始,会经过两条流水线,上面的流水线是 mBTB + SHP 负责更精确的预测,下面的流水线是查询 ITLB + 读取 ICache。上面说 2-bubble 的 mBTB,实际上就是从 B1 得到 Fetch Window,B2 读取 mBTB 和 SHP 的权重,等到 B3 完成之后才可以计算出结果,判断是 taken 还是 not taken,如果预测的结果和 uBTB 预测不一致,就需要刷流水,从 B1 重新开始:B1 B2 B3 B1 B2 B3,三个周期一个 taken branch。

图中也可以看到 vBTB 在 B4,所以如果一个 cacheline 有超过 8 个 branch,那么在预测这些溢出到 vBTB 中的分支时,需要额外的两个周期:B1 B2 B3 B4 B3,五个周期一个 taken branch。

Exynos M3 继续改进了分支预测器。首先是 uBTB 的图的容量翻倍,添加了针对无条件分支的容量。为了加速总是跳转的条件分支指令,当 mBTB 检测到总是跳转的条件分支指令时,提前一个周期得到结果,也就是上图中 B3 到 B1 的连线,没有经过权重计算,直接刷 B1:B1 B2 B1 B2,两个周期一个 taken branch,1-bubble,在论文里叫做 1AT(1-bubble Always Taken)。Exynos M3 还翻倍了 SHP 的行的个数,也翻倍了 L2 BTB 容量。

Exynos M4 继续翻倍了 L2 BTB 容量,减少了 L2 BTB refill 到 mBTB 的延迟,带宽翻倍。这个优化主要是针对分支比较多,mBTB 存不下的程序。

Exynos M5 增加了 Empty Line Optimization 优化:检测没有分支的缓存行,如果确认缓存行没有分支,那就不用预测里面的分支了,可以节省功耗。

为了进一步优化 taken branch 的吞吐,在 mBTB 中记录分支的目的地址时,不仅记录在分支本身所在的 mBTB entry 中,还要记录在这条分支的前序分支的 mBTB entry 中:例如 A 跳转到 B,B 跳转到 C,经典的实现是用 A 的地址找到 A 的 BTB entry,entry 中记录了 A 目的地址是 B,接着用 B 的地址找到 B 的 BTB entry,entry 中记录了 B 的目的地址是 C;而 Exynos M5 的设计是,C 的目的地址,不仅要记录在 B 的 BTB entry 中,还要记录在 A 的 BTB entry 中。不过这里要求 B 的跳转是 always-taken 或者 often-taken,因为并没有对第二条分支做预测,而是预测它一定会跳。通过这样的方法,可以在 2-bubble 的预测器的实现下,实现 1 taken branch/cycle 的吞吐,等效于一个 0-bubble 的预测器。下面是论文中对 mBTB 从 2-bubble 到 1-bubble 最终到 0-bubble 的变化的对比图:

最左边的 SHP 2-bubble 就是最初的实现,它需要在 B3 得到分支是 taken 还是 not taken 的信息,如果和之前预测的不一样,那就需要 flush 掉流水线,B1 重新从正确的地址开始,然后重复这个过程,由于每次都需要到 B3 才能得到正确的地址,所以三个周期一个 taken branch。

中间是改进的 1AT 1-bubble 实现,它在 mBTB 中记录了这个分支是否是 Always Taken。因为 mBTB 在 B2 中读取,所以这个信息在 B2 就可以得知,例如图中 A 分支到达 B2 时,检测到它是 Always Taken,下个周期的 B1 直接就从正确的地址 B 开始,同理 B 到达 B2 时,又发现它是 Always Taken,下一个周期的 B1 又从 C 开始,所以两周期一个 taken branch,前提是分支是 Always Taken,通过避免分支预测来减少一个周期的 bubble。

右边是最后的 ZAT/ZOT 0-bubble 实现,它在 mBTB 中记录了后续两个分支的地址,例如 X 在 mBTB 中记录了 A 和 B 的地址。当 B3 发现 X 要跳转的时候,刷流水线,在接下来的两个周期里分别给 B1 提供了 A 和 B 的地址。当 A 到达 B2 时,A 在 mBTB 里记录了 B 和 C 的地址,于是把 C 的地址转发到下一个周期的 B1,依此类推,B2 的 B 得到了 D 的地址,B2 的 C 得到了 E 的地址,这样实现了每个周期一个 taken branch。通过记录两跳的地址和避免分支预测(把 Always Taken 和 Often Taken 都预测为 Taken),两个周期给出两个地址,减少两个周期的 bubble。

这时候就相当于有两个 0-bubble 预测器了,mBTB 有 0-bubble 能力,uBTB 也有,所以 Exynos M5 减少了 uBTB 的容量,换取更大的 mBTB 的预测器容量:SHP 的表数量翻倍,GHR 历史长度增加。

虽然 mBTB 在特定情况下可以做到 0-bubble,但是如果总是需要纠正预测错误,就会回退到三个周期一条分支的性能。为了解决这个问题,Exynos M5 引入了 Mispredict Recovery Buffer(MRB):针对比较难预测的分支,记录它后续最可能执行的三次 fetch 的地址,如果命中了 MRB,那就直接从 MRB 中按顺序用三个周期把这三次 fetch 的地址放到 B1,然后流水线去验证这三个 fetch 地址是否正确,节省了重复的 B3 到 B1 的重定向时间。这个思路有点像大模型的推测生成:用比较短的时间预测(在这里是直接用 MRB 记下来了)出一个本来是串行的过程的结果,然后再用流水线或者并行的方式去验证结果是否正确。利用的性质都是,串行生成慢,但是验证结果却比较快。

Exynos M6 扩大了 mBTB 容量,针对间接跳转指令做了更多的优化,主要是考虑应用程序会出现一个间接跳转会跳转到上百个不同的目的地址这种模式,之前的 VPC 方法是 O(n) 的,n 是可能的目的地址的个数,n 小的时候比较好,n 大了就很慢了。

Exynos M6 的办法是,针对这些目的地址特别多的间接跳转指令,设计单独的存储,不去占用 vBTB 的空间,这个空间是 Indirect target storage,采用 4 路组相连,一共 256 个 set。经常出现的目的地址还是和之前一样,放在 mBTB 中,但是对于剩下的目的地址,则是放到 Indirect target storage 中,根据最近的 indirect branch target 计算出 Index 和 Tag,去 Indirect target storage 中寻找,其实这个就和 ITTAGE 里的一个 table 有点类似了。

最后论文总结了 Exynos 从 M1 到 M6 的各级分支预测器的存储面积开销,基本每一代都有所增加,既有 MPKI 的减少(M6 相比 M1 在 SPECint2006 上 MPKI 减少 25.6%),又有预测性能的提升。

分支预测安全

有意思的,论文也提到了分支预测的安全问题,主要是避免跨上下文的分支预测器注入攻击。核心思路是,给每个上下文生成一个随机数(CONTEXT_HASH),然后把随机数异或到 BTB 保存的目的地址里面去,在从 BTB 读出来目的地址使用之前,要再次异或同一个随机数。那么如果是读取了来自同一个上下文的 BTB entry,通过两次异或可以得到正确的原始数据;如果是读取了来自不同上下文的 BTB entry,由于随机数不同,最后会得到随机的数据。当然了,前提是这些随机数不能被攻击者得到,对软件是不可见的。

uOP Cache

Exynos M1 到 M4 没有 uOP Cache,所有指令都需要经过取指和译码,得到 uOP。从 Exynos M5 开始引入了 uOP Cache,会缓存译码后的 uOP。Exynos M5 的 uOP Cache 最多可以保存 384 个 uOP,每个 entry 可以保存 6 个 uOP,一个周期提供一个 entry。一个 uOP Cache Entry 中的 uOP 来自连续的指令,以分支指令作为结尾。下面是论文给出的一个例子:

这段指令的入口在第一个缓存行的最后,从 I0 开始,执行 I0 I1 I2 I3,I3 是一条分支指令,因此 uOP 的 entry 到此结束,记录了 I0-I3 译码后的 uOP,这里 I2 指令被译码成了两条 uOP,于是这个 entry 就是 U0 U1 U2A U2B U3 这五个 uOP。I3 跳转到了 I4,I4 紧接着又是一条分支指令 I5,所以 uOP 的 entry 到这里结束,记录 I4 和 I5 译码后的 uOP:U4 和 U5。后面依此类推。

那么什么时候 uOP Cache 启用呢?论文中提到了一个状态机:

  1. FilterMode:当 uBTB 在学习分支之间的关系时,也在检查这段代码能否放到 uOP Cache 里,如果可以的话,转移到 BuildMode
  2. BuildMode:开始把译码得到的 uOP 保存到 uOP Cache 内部,同时和 uBTB 学习到的图进行比对,当图中大部分的边对应的指令都已经被 uOP Cache 学习到,说明 uOP Cache 已经捕捉了大部分需要的指令,进入 FetchMode
  3. FetchMode:指令缓存和译码部件被关闭,节省功耗,所有指令都从 uOP Cache 提供;此时如果 uBTB 的准确率很高,mBTB 也可以关掉,进一步节省功耗。当 uOP Cache 命中率降低,切换回 FilterMode

这种设计还是比较常见的,uOP Cache 和 Decoder 不会同时工作,而是二选一,根据 uOP Cache 的命中率来决定谁来工作。然后进一步为了避免 uOP Cache 填充的功耗,如果 uBTB 发现这些代码放不下 uOP Cache 中,就不填充 uOP Cache 了,这就是 FilterMode 的设计的意义。

L1 数据预取

L1 数据预取器会检测不同的 stride,在虚拟地址上,跟踪访存的历史,为了方便识别访存的序列,由于访存是可以乱序执行的,它会进行重排,使得访存模式的识别看到就是程序的顺序。为了验证预取是否正确,在预取的同时,也会把这些预取的地址记录下来,放在 Confirmation Queue 中,和未来的 load 地址做比对。如果预取器和未来的 load 匹配的比例很高,说明预取器很准确,可以继续让他预取。如果准确率较低,为了避免浪费内存带宽,就会停止预取。

除了 strided 访存模式,Exynos M3 还引入了 Spatial Memory Stream 预取器,它会跟踪一个区间的第一次 cache miss 和后续的 miss,当再次遇到第一次 cache miss 的地址时,预取后续可能会出现 miss 的地址。

L2/L3 缓存

为了减少数据的重复,L3 缓存和 L1/L2 是 exclusive 的关系,数据是互斥的,要么存在 L3 里,要么存在 L1/L2 里,要么都不存。

Exynos M4 针对 L2 缓存引入了 Buddy Prefetcher:如果一个缓存行缺失了,那就把它相邻的下一个缓存行也预取进来。

访存延迟

Exynos 系列的 load to use latency 通常情况下是 4 cycle,但针对 load to load 的情况,也就是前一个 load 的结果,作为后一个 load 的基地址的情况,从 Exynos M4 开始可以做到 3 cycle 的 load to use latency,这在论文中叫做 cascading load。这个 4 cycle 减到 3 cycle 的特性在苹果,高通和 Intel(E-core)的 CPU 中都有看到。

小结

虽然 Exynos 系列微架构的芯片没有新的演进了,但是也非常感谢这些作者慷慨地介绍了他们这些年优化微架构的努力,提供了很多有价值的信息。从作者信息也可以看到,当时开发 Exynos 的团队成员,在团队解散以后,去的基本也是有自研核的公司:Sifive,Centaur,ARM,AMD,Nuvia。

由于本人对 DCache 以及 Prefetch 部分缺乏深入了解,所以这部分的介绍比较少,有兴趣的读者建议参考原文。

🔲 ⭐

编译monero For macOS ARM

前言

水💦篇文章.

monero-gui官方没给ARM版.要下个版本应该会给?急急急,转译的应用很不爽.直接上编译.

这个项目里面整的很清楚了,但我还是要水💦.

前提: 你已安装了brew

如果不是非要ARM版本(注:本文发布时是没有arm版的,以后可能直接就是arm版,不用编译),可以直接用brew安装.CLI版:brew install monero GUI版:brew install monero-wallet 后续更新方便.

安装依赖

1
2
3
brew remove qt # 起手卸载,防止和qt@5冲突
brew install cmake pkg-config openssl boost unbound hidapi zmq libpgm libsodium miniupnpc expat libunwind-headers protobuf libgcrypt qt@5
brew reinstall qt@5 # 可选

编译

找个地方放编译文件夹,建议编译完不用删,方便以后编译,如果硬要删又要编译.我建议看我后面关于签名部分.

1
2
3
git clone --recursive https://github.com/monero-project/monero-gui.git
cd monero-gui
make -j$(nproc) || make -j1

编译没问题往后看,有问题自己解决吧(我不会).

如果编译文件夹不删除.那么请看本部分

1
cp -rf ./build/release/bin/monero-wallet-gui.app /Applications/monero-wallet-gui.app

构建应用程序包

用于要删文件夹情况.

1
2
3
4
5
mkdir build && cd build
cmake -D CMAKE_BUILD_TYPE=Release -D ARCH=default ..
make -j$(nproc) || make -j1
make deploy
cd ./build/bin

新建文件entitlements.plist

1
2
3
4
5
6
7
8
9
10
cat << EOF > entitlements.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.disable-executable-page-protection</key>
<true/>
</dict>
</plist>
EOF

然后运行下面命令,如果你有苹果开发者证书应该知道XXXXXXXXXX填什么.如果没有,别运行下面命令.看后面.

1
2
sudo codesign --deep --force --verify --verbose --options runtime --timestamp --entitlements entitlements.plist --sign 'XXXXXXXXXX' monero-wallet-gui.app
cp -rf ./monero-wallet-gui.app /Applications/monero-wallet-gui.app

没有苹果开发者证书

将上面<true/>改为<false/>

打开钥匙串访问.app,然后在 登录 我的证书 这个界面下.然后看图

image-20230827225123369image-20230827225144374

名称可以自己改.然后运行下面命令.

1
2
3
sudo codesign --deep --force --verify --verbose --options runtime --timestamp --entitlements entitlements.plist --sign 'lanyun' monero-wallet-gui.app
spctl -a -t exec -vv monero-wallet-gui.app # 看一下,是不是accepted,如果是,则继续
cp -rf ./monero-wallet-gui.app /Applications/monero-wallet-gui.app

如果有问题,请自行参考 https://github.com/monero-project/monero-gui/blob/master/DEPLOY.md

如果你遇到 libboost_atomic-mt.dylib (no such file) 这个错误.

显然,出现了问题.有种错误修复方式,因为会导致显示异常.

cp -rf /opt/homebrew/lib/libboost_atomic-mt.dylib ./monero-wallet-gui.app/Contents/Frameworks

本人不会修,可能是macOS版本太高了吧,无所谓,我不用构建应用程序包.

🔲 ☆

我两周就写了三行代码 - ARM Cortex A9 中断与浮点数运算、FPU 问题

问题出现

公司产品采用了 Xilinx Zynq 7z010 芯片,用于运动控制以及网络通讯。两周前,测试过程中发现网络通信会小概率出错,TCP 收到的数据 CRC 校验失败,无法稳定复现。

设备平台概述:

  1. CPU: Cortex-A9 双核
  2. RAM: 1GB DDR3
  3. 操作系统: FreeRTOS
  4. 网络协议栈: lwip211

定位过程

怀疑应用层数据处理问题

TCP 是二进制数据流,每个包的长度不固定,应用层也许会写错。于是我修改了应用层的处理方案,手动构造了定长的数据包,虽然会导致 TCP 流量大幅上涨,但是逻辑看起来更清晰。

然而,修改后,似乎由于流量变大了,原来小概率出现的错误,现在大概率会出现!这也给 Debug 带来了有利的一面。

怀疑网络通讯链路电磁干扰问题

但是这个怀疑方向很快就被否定了,因为我用了 TCP 协议,理论上只可能超时,不可能出错。

怀疑 lwip 接口调用问题

lwip 有多个 TCP API,之前用的 Socket API,我尝试换成了 RAW API,但是问题依旧。

在调试的过程中,我尝试在网络链路的每一层数据打印出来,惊奇地发现,在数据链路层,数据是正确的!然而 lwip 的代码冗杂且数 MB 数据中才会出现几个错误位,于是我暂时没有考虑一层层分析代码。

怀疑与其他线程之间存在干扰,或者存在数组越界访问

这样 Debug 就很简单了。我关闭了所有其他的线程,不出所料,Bug 消失了。

一点点放开线程,发现是一个运动控制的硬中断造成的 Bug。

然后再“二分法”排除代码,结果排除到最后,仅仅是一行代码:

1
2
// b, c 也为 long long
long long a = (long long)((double)b * (double)c);

这让我大跌眼镜,因为实验证明,把这句话删了,TCP 通讯就正常了。

怀疑是浮点运算的问题

更加让我迷惑的是,把上述语句改下,同样也没问题了:

1
long long a = b * c;

进一步定位:我在另一个线程中添加了浮点运算,并把这个有影响的中断关闭,TCP 通讯同样出问题了。

就此,几乎可以确定是浮点数运算造成的问题了。

问题小结

一句话描述问题:在中断或某个线程中进行浮点数操作,会导致另一个 TCP 通讯线程数据出错。

说实话,我当时也没法理解其中的联系。

只不过我们用的芯片自带双精度 FPU(浮点运算单元),也许是 FPU 的问题?

解决过程

查找资料

关键词 lwip tcp receive wrong datazynq float process corrupt memory 等关键词,都没有找到有价值的解决方案。

求助 Xilinx 技术支持

果然用微信联系的技术支持不靠谱,上午说帮忙复现,下午就没信了。

求助朋友圈资深开发者

只可惜他们都是互联网界的大佬,只有我在嵌入式开发领域摸爬滚打,他山之玉难以攻石。

求助 V2EX 网友

发了帖子 在这里

V 站网友给了非常有价值的线索:

  1. 网友 A 称他们使用同样的平台出现过类似的问题。他们的解决方案是,进行浮点数操作之前,关闭所有的中断;
  2. 网友 B 分析可能 正在计算浮点数的时候,刚好发生了 systick 线程切换,但是线程切换过程中,没有保存 /恢复浮点寄存器
  3. 网友 C 更是找到了相关文章:

    “Some GCC libraries optimise memory copy and memory set (and possibly other) functions by making use of the wide floating point registers. Therefore, by default, any task that uses functions such as memcpy(), memcmp() or memset(), or uses a FreeRTOS API function such as xQueueSend() which itself uses memcpy(), will inadvertently corrupt the floating point registers.”

真可谓一针见血,TCP 协议栈中大量使用了 memcpy,而 memcpy 又使用了 FPU 的寄存器,极有可能在 TCP 处理数据的过程中,另一个中断来了,进行了浮点运算并修改了 FPU 的寄存器,以致 TCP 数据出错。

同样根据网友的指点,看了这篇文章 Using FreeRTOS on ARM Cortex-A9 Embedded Processors,原来 FreeRTOS 自身已经考虑了 FPU 与上下文切换相关的问题,只是要我们将 configUSE_TASK_FPU_SUPPORT 这个宏定义为 2 即可。

问题解决

花了些时间进行 FPU 寄存器相关的搜索,依照 这篇文章 ,对 FPU 的寄存器做了相关处理,总结起来就三行代码:

第一行代码

在中断响应函数开头添加以下代码:

1
__asm("VPUSH {d0-d15}"); // FPU 寄存器入栈

第二行代码

在中断响应函数末尾添加以下代码:

1
__asm("VPOP {d0-d15}"); // FPU 寄存器出栈

第三行代码

FreeRTOS 启用 FPU 支持相关宏:

1
#define configUSE_TASK_FPU_SUPPORT 2

至此,问题解决。

❌