<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>编译器 on 止语Lab</title>
        <link>https://www.wujiachen.com.cn/tags/%E7%BC%96%E8%AF%91%E5%99%A8/</link>
        <description>Recent content in 编译器 on 止语Lab</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>zh-cn</language>
        <lastBuildDate>Tue, 14 Apr 2026 00:55:51 +0800</lastBuildDate><atom:link href="https://www.wujiachen.com.cn/tags/%E7%BC%96%E8%AF%91%E5%99%A8/index.xml" rel="self" type="application/rss+xml" /><item>
            <title>你写的Go代码，编译器能&#34;看懂&#34;多少</title>
            <link>https://www.wujiachen.com.cn/posts/go-compiler-optimization/</link>
            <pubDate>Tue, 14 Apr 2026 00:55:49 +0800</pubDate>
            <guid>https://www.wujiachen.com.cn/posts/go-compiler-optimization/</guid>
            <description>&lt;img src=&#34;https://www.wujiachen.com.cn/&#34; alt=&#34;Featured image of post 你写的Go代码，编译器能&#34;看懂&#34;多少&#34; /&gt;&lt;p&gt;&lt;img alt=&#34;封面&#34; class=&#34;gallery-image&#34; data-flex-basis=&#34;565px&#34; data-flex-grow=&#34;235&#34; height=&#34;672&#34; loading=&#34;lazy&#34; sizes=&#34;(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px&#34; src=&#34;https://img.wujiachen.com.cn/go-compiler-optimization/cover.png!/watermark/text/5q2i6K+tTGFi/size/20/color/666666/opacity/70/align/southeast&#34; srcset=&#34;https://www.wujiachen.com.cn/cover_8617774111593452540_hu_f75657df86fbfd2a.png 800w, https://img.wujiachen.com.cn/go-compiler-optimization/cover.png!/watermark/text/5q2i6K+tTGFi/size/20/color/666666/opacity/70/align/southeast 1584w&#34; width=&#34;1584&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;凌晨两点，编译器在优化你的代码。&lt;/p&gt;&#xA;&lt;p&gt;它看到一个函数调用，小函数，就两行。它把函数体&amp;quot;展开&amp;quot;塞进调用点，省掉了一次函数跳转。然后它继续往下看，看到一个接口调用——停下了。它不知道运行时拿到的是哪个具体类型，没办法展开，只好老老实实做动态分发。&lt;/p&gt;&#xA;&lt;p&gt;同一个加法操作，编译器&amp;quot;看得懂&amp;quot;和&amp;quot;看不懂&amp;quot;，性能差了好几倍。&lt;/p&gt;&#xA;&lt;p&gt;这不是夸张。这是我实测的数据：直接调用一个小函数，262 纳秒；同样的逻辑通过接口调用，1478 纳秒。代码做的事一模一样，编译器的&amp;quot;视野&amp;quot;不同，结果天差地别。&lt;/p&gt;&#xA;&lt;p&gt;你可能觉得这是编译器的事，跟你写代码的方式没关系。但事实是：你的每一行代码都在决定编译器能&amp;quot;看见&amp;quot;多少。内联、逃逸分析、边界检查消除，这些优化不是独立发生的，它们像多米诺骨牌一样一环扣一环。一环断，全链断。&lt;/p&gt;&#xA;&lt;p&gt;这篇文章不讲&amp;quot;编译器能做什么&amp;quot;——这种文章已经够多了。我讲的是另一个方向：&lt;strong&gt;编译器什么时候帮不了你&lt;/strong&gt;，以及你怎么写代码，才能让编译器看得懂。&lt;/p&gt;&#xA;&lt;p&gt;全文分六步：内联的边界 → 逃逸分析的边界 → 优化链（核心）→ 编译器盲区 → 写给编译器看的代码。&lt;/p&gt;&#xA;&lt;h2 id=&#34;一编译器怎么看你的代码&#34;&gt;&lt;a href=&#34;#%e4%b8%80%e7%bc%96%e8%af%91%e5%99%a8%e6%80%8e%e4%b9%88%e7%9c%8b%e4%bd%a0%e7%9a%84%e4%bb%a3%e7%a0%81&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;一、编译器怎么&amp;quot;看&amp;quot;你的代码&#xA;&lt;/h2&gt;&lt;p&gt;&lt;img alt=&#34;概念图：小编译器手电筒光圈&#34; class=&#34;gallery-image&#34; data-flex-basis=&#34;321px&#34; data-flex-grow=&#34;133&#34; height=&#34;896&#34; loading=&#34;lazy&#34; sizes=&#34;(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px&#34; src=&#34;https://img.wujiachen.com.cn/go-compiler-optimization/ch1-vision.png!/watermark/text/5q2i6K+tTGFi/size/20/color/666666/opacity/70/align/southeast&#34; srcset=&#34;https://www.wujiachen.com.cn/ch1-vision_15236391899101622439_hu_187762927b97be1a.png 800w, https://img.wujiachen.com.cn/go-compiler-optimization/ch1-vision.png!/watermark/text/5q2i6K+tTGFi/size/20/color/666666/opacity/70/align/southeast 1200w&#34; width=&#34;1200&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;想象编译器是一个戴着眼镜的小家伙，坐在你的代码前面仔细阅读。它有一盏手电筒——光圈照到的地方，它看得清清楚楚；光圈外面，一片漆黑。&lt;/p&gt;&#xA;&lt;p&gt;这盏手电筒的光圈大小，取决于你写代码的方式。&lt;/p&gt;&#xA;&lt;p&gt;函数很小、逻辑简单？光圈够大，编译器一眼看穿，顺手就优化了。函数里有接口调用、闭包、反射？光圈突然变窄，编译器看不清上下文，只能做最保守的选择。&lt;/p&gt;&#xA;&lt;p&gt;关键在于：编译器的每一个优化决策，都建立在&amp;quot;我看到了什么&amp;quot;的基础上。它看到的上下文越多，能做的优化越多；看到的越少，越保守。&lt;/p&gt;&#xA;&lt;p&gt;有人会说：&amp;ldquo;过早优化是万恶之源。&amp;ldquo;没错——但理解编译器不是过早优化。了解交通规则不是让你开快车，是让你不在该刹车的时候踩油门。写编译器友好的代码，是&amp;quot;写对的前提下不浪费编译器能力&amp;rdquo;。&lt;/p&gt;&#xA;&lt;p&gt;那编译器到底能看懂什么，看不懂什么？&lt;/p&gt;&#xA;&lt;h2 id=&#34;二内联编译器最爱的优化&#34;&gt;&lt;a href=&#34;#%e4%ba%8c%e5%86%85%e8%81%94%e7%bc%96%e8%af%91%e5%99%a8%e6%9c%80%e7%88%b1%e7%9a%84%e4%bc%98%e5%8c%96&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;二、内联——编译器最爱的优化&#xA;&lt;/h2&gt;&lt;p&gt;编译器最想做的一件事，是把函数调用&amp;quot;展开&amp;rdquo;。&lt;/p&gt;&#xA;&lt;p&gt;你写了一个 &lt;code&gt;add(a, b int) int&lt;/code&gt;，只有一行 &lt;code&gt;return a + b&lt;/code&gt;。它看到调用 &lt;code&gt;add(x, y)&lt;/code&gt; 的地方，直接把函数体塞进去，变成 &lt;code&gt;x + y&lt;/code&gt;。函数调用的开销，压栈、跳转、返回，全部消失了。&lt;/p&gt;&#xA;&lt;p&gt;这叫内联（inlining）。它是编译器最基础也最爱的优化。&lt;/p&gt;&#xA;&lt;p&gt;但它不是什么函数都能内联。它有预算。Go 编译器给每个函数算了一个&amp;quot;复杂度分数&amp;quot;，分数太高就不内联——预算有限，不能无限制展开。更关键的是，有些代码模式直接让内联变得不可能。&lt;/p&gt;&#xA;&lt;p&gt;我跑了一组对照实验：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;9&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// 内联友好：直接调用小函数&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;add&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;a&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;b&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;a&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;+&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;b&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// 内联不友好：通过接口动态分发&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Calculator&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;interface&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Calculate&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;a&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;b&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;用 &lt;code&gt;-gcflags=&amp;quot;-m&amp;quot;&lt;/code&gt; 看编译器的决策：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;./inline_test.go:10:6: can inline add&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;./inline_test.go:18:13: inlining call to add       ← 直接调用：内联成功&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;// 接口版本——没有 &amp;#34;inlining call&amp;#34; 也没有 &amp;#34;devirtualizing&amp;#34;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;// 编译器看不到运行时的具体类型，放弃了&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;Benchmark 数据更直白：&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;写法&lt;/th&gt;&#xA;          &lt;th style=&#34;text-align: right&#34;&gt;ns/op&lt;/th&gt;&#xA;          &lt;th style=&#34;text-align: right&#34;&gt;allocs/op&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;直接调用（内联）&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;~262&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;0&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;接口调用（不内联）&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;~1478&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;0&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;（测试环境：Go 1.26，Apple Silicon M4 Pro，单次 benchmark 取中位数）&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;5.6 倍差距&lt;/strong&gt;。逻辑完全一样，allocs 都是零——差异纯粹来自分发方式。&lt;/p&gt;&#xA;&lt;p&gt;这可能让你觉得奇怪：新版 Go 不是有去虚化（devirtualization）吗——从 Go 1.22 开始，编译器就能交错执行去虚化和内联了。确实有。编译器&amp;quot;猜到了&amp;quot;接口背后的真身——这叫去虚化。当它看到 &lt;code&gt;var p Processor = fastProcessor{}&lt;/code&gt; 这种具体类型赋值时，它会自动去虚化：&lt;code&gt;devirtualizing p.Process to fastProcessor&lt;/code&gt;，然后内联，零开销。&lt;/p&gt;&#xA;&lt;p&gt;但去虚化有个前提——&lt;strong&gt;它得&amp;quot;看见&amp;quot;具体类型&lt;/strong&gt;。如果类型从 slice、map、channel 里取出来，编译器在编译期无法确定运行时拿到的是哪个，去虚化就失败了。&lt;/p&gt;&#xA;&lt;p&gt;所以不是&amp;quot;用接口就慢&amp;quot;，是&amp;quot;编译器看不清具体类型就慢&amp;quot;。&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;场景图：内联成功 vs 无法内联&#34; class=&#34;gallery-image&#34; data-flex-basis=&#34;430px&#34; data-flex-grow=&#34;179&#34; height=&#34;768&#34; loading=&#34;lazy&#34; sizes=&#34;(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px&#34; src=&#34;https://img.wujiachen.com.cn/go-compiler-optimization/ch2-inline.png!/watermark/text/5q2i6K+tTGFi/size/20/color/666666/opacity/70/align/southeast&#34; srcset=&#34;https://www.wujiachen.com.cn/ch2-inline_9562941588448846566_hu_61884e2e20511aa.png 800w, https://img.wujiachen.com.cn/go-compiler-optimization/ch2-inline.png!/watermark/text/5q2i6K+tTGFi/size/20/color/666666/opacity/70/align/southeast 1376w&#34; width=&#34;1376&#34;&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;三逃逸分析栈还是堆&#34;&gt;&lt;a href=&#34;#%e4%b8%89%e9%80%83%e9%80%b8%e5%88%86%e6%9e%90%e6%a0%88%e8%bf%98%e6%98%af%e5%a0%86&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;三、逃逸分析——栈还是堆？&#xA;&lt;/h2&gt;&lt;p&gt;我在跑 benchmark 时发现一件怪事：同一个函数，加了 &lt;code&gt;//go:noinline&lt;/code&gt; 就逃逸到堆上，不加就不逃逸——编译器在帮你&amp;quot;作弊&amp;quot;。&lt;/p&gt;&#xA;&lt;p&gt;先不急着解释，看基础知识。看完函数后，要决定变量放哪：栈还是堆。&lt;/p&gt;&#xA;&lt;p&gt;栈分配几乎免费，函数返回时自动回收。堆分配就麻烦了：GC 要扫描、标记、清理，每次都要走一遍。&lt;/p&gt;&#xA;&lt;p&gt;它用逃逸分析（escape analysis）做这个决策：变量寿命不超过函数返回，放栈上；变量&amp;quot;逃出&amp;quot;了函数——被返回指针引用、被闭包捕获、被塞进接口：必须放堆上。&lt;/p&gt;&#xA;&lt;p&gt;我实测了几种常见场景：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// ✅ 栈分配：值返回，不逃逸&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;returnByValue&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;x&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;42&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;x&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// ❌ 堆分配：返回指针，逃逸&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;returnByPointer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;x&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;42&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;x&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;   &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// moved to heap: x&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// ❌ 堆分配：闭包捕获，逃逸&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;closureCapture&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;x&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;42&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;x&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// func literal escapes to heap&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;Benchmark：&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;写法&lt;/th&gt;&#xA;          &lt;th style=&#34;text-align: right&#34;&gt;ns/op&lt;/th&gt;&#xA;          &lt;th style=&#34;text-align: right&#34;&gt;allocs/op&lt;/th&gt;&#xA;          &lt;th style=&#34;text-align: center&#34;&gt;vs 栈分配&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;值返回（栈）&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;~0.74&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;0&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;1x&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;返回指针（堆）&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;~6.6&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;1&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;~9x&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;闭包捕获（堆）&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;~7.8&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;1&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;~10.5x&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&#xA;    &lt;blockquote&gt;&#xA;        &lt;p&gt;💡 栈分配 vs 堆分配：近 10 倍差距——这就是为什么逃逸分析这么重要&lt;/p&gt;&#xA;&#xA;    &lt;/blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;9 到 10 倍差距&lt;/strong&gt;（这是单个 int 的极端场景，真实业务中 struct 的差距通常小得多）。如果你的热路径上有大量小对象的堆分配，GC 压力会成倍增加——更多堆分配意味着更频繁的 GC，更频繁的 GC 意味着更多 CPU 时间花在回收上。&lt;/p&gt;&#xA;&lt;p&gt;栈和堆的差距一目了然。但回到开头那个怪事——&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;不加 &lt;code&gt;//go:noinline&lt;/code&gt; 时，上面所有&amp;quot;应该逃逸&amp;quot;的函数，benchmark 里都是 0 allocs。&lt;/strong&gt; 为什么？因为函数被内联后，编译器看到的上下文变了——变量不再&amp;quot;逃出&amp;quot;函数边界，逃逸分析自然就把它放回栈上了。&lt;/p&gt;&#xA;&lt;p&gt;这不是 bug，是特性。它恰好证明了下一章的核心概念——优化链。内联失败，逃逸分析就更保守，更多变量被迫分配到堆上。&lt;/p&gt;&#xA;&lt;p&gt;想知道编译器对你的代码做了什么逃逸决策？一行命令：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;go build -gcflags&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;-m&amp;#34;&lt;/span&gt; ./...&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;输出里 &lt;code&gt;does not escape&lt;/code&gt; 是好消息，&lt;code&gt;escapes to heap&lt;/code&gt; 或 &lt;code&gt;moved to heap&lt;/code&gt; 要注意。&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;场景图：栈还是堆的十字路口&#34; class=&#34;gallery-image&#34; data-flex-basis=&#34;321px&#34; data-flex-grow=&#34;133&#34; height=&#34;896&#34; loading=&#34;lazy&#34; sizes=&#34;(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px&#34; src=&#34;https://img.wujiachen.com.cn/go-compiler-optimization/ch3-escape.png!/watermark/text/5q2i6K+tTGFi/size/20/color/666666/opacity/70/align/southeast&#34; srcset=&#34;https://www.wujiachen.com.cn/ch3-escape_13488142986122360702_hu_4633f6f35027a069.png 800w, https://img.wujiachen.com.cn/go-compiler-optimization/ch3-escape.png!/watermark/text/5q2i6K+tTGFi/size/20/color/666666/opacity/70/align/southeast 1200w&#34; width=&#34;1200&#34;&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;四优化链一环断全链断&#34;&gt;&lt;a href=&#34;#%e5%9b%9b%e4%bc%98%e5%8c%96%e9%93%be%e4%b8%80%e7%8e%af%e6%96%ad%e5%85%a8%e9%93%be%e6%96%ad&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;四、优化链——一环断，全链断&#xA;&lt;/h2&gt;&lt;p&gt;内联、逃逸分析、接口去虚化——前面三章各讲了一个。但你可能没意识到，它们之间有一条暗线。&lt;/p&gt;&#xA;&lt;p&gt;Go 编译器的优化不是六个独立的开关，是一条链。内联在前面，为后续所有优化提供上下文。内联成功，它看到更多，逃逸分析更精确，去虚化可能生效，后续 SSA pass 有更多素材。内联一旦失败，上下文就丢了。后面的逃逸分析只能做最保守的假设，去虚化也找不到入口，SSA pass 拿到的素材就少了——整条链从源头断开。&lt;/p&gt;&#xA;&lt;p&gt;Go 官方源码 &lt;code&gt;cmd/compile/internal/ssa/compile.go&lt;/code&gt; 里，核心 SSA pass 按固定顺序执行（实际有几十个，这里只列关键的）：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;内联（前端）→ 逃逸分析（前端）→ SSA 优化管线：&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  优化(opt) → 公共子表达式消除(CSE) → 空指针检查消除 → 边界检查证明(prove) → 死代码清理 → 降级(lower) → 寄存器分配&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;注意，内联和逃逸分析不在 SSA 管线里——它们在更早的编译阶段完成（编译器先做内联和逃逸分析，再进入 SSA 优化管线）。SSA 拿到的已经是经过内联和逃逸分析后的代码。&lt;strong&gt;前端优化为后端优化铺路，一环扣一环。&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;我跑了一组实验来验证这个链式传导：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// 优化链完整：小函数被内联 → 编译器看到完整上下文&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;chainComplete&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sum&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1000&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;++&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sum&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;+=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;addInline&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;+&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 内联成功&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sum&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// 优化链断裂：接口调用阻断内联 → 后续优化链式失效&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;chainBroken&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;adders&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[]&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Adder&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;inlineAdder&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{}}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;a&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;adders&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 动态分发，无法去虚化，无法内联&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sum&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1000&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;++&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sum&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;+=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;a&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Add&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;+&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sum&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;结果：&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;场景&lt;/th&gt;&#xA;          &lt;th style=&#34;text-align: right&#34;&gt;ns/op&lt;/th&gt;&#xA;          &lt;th style=&#34;text-align: right&#34;&gt;allocs/op&lt;/th&gt;&#xA;          &lt;th style=&#34;text-align: center&#34;&gt;vs 基准&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;纯直接计算（最优）&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;~485&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;0&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;1x&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;优化链完整&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;~488&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;0&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;~1x&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;优化链断裂&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;~1043&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;0&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;~2.1x&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;优化链完整版几乎等于手写直接计算——内联让函数调用&amp;quot;消失&amp;quot;了。优化链断裂版慢了两倍，不是因为某个优化没做，而是因为第一个优化（内联）失败了，后面一串都跟着失效。&lt;/p&gt;&#xA;&lt;p&gt;这就是&amp;quot;一环断，全链断&amp;quot;。严谨地说，2.1 倍的差异里有多少来自内联失败本身、多少来自逃逸分析变保守，本文没有逐一拆解——但方向是明确的：源头断了，下游必然受影响。&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;场景图：多米诺骨牌优化链&#34; class=&#34;gallery-image&#34; data-flex-basis=&#34;430px&#34; data-flex-grow=&#34;179&#34; height=&#34;768&#34; loading=&#34;lazy&#34; sizes=&#34;(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px&#34; src=&#34;https://img.wujiachen.com.cn/go-compiler-optimization/ch4-chain.png!/watermark/text/5q2i6K+tTGFi/size/20/color/666666/opacity/70/align/southeast&#34; srcset=&#34;https://www.wujiachen.com.cn/ch4-chain_9958708004728156512_hu_7f73a12f93efe3f1.png 800w, https://img.wujiachen.com.cn/go-compiler-optimization/ch4-chain.png!/watermark/text/5q2i6K+tTGFi/size/20/color/666666/opacity/70/align/southeast 1376w&#34; width=&#34;1376&#34;&gt;&lt;/p&gt;&#xA;&#xA;    &lt;blockquote&gt;&#xA;        &lt;p&gt;&lt;strong&gt;旁注：边界检查消除（BCE）&lt;/strong&gt;。Go 每次访问数组/切片都做边界检查，但编译器的 &lt;code&gt;prove&lt;/code&gt; pass 会尝试证明索引一定在范围内，证出来就消掉检查。BCE 本身性能收益不大（现代 CPU 分支预测器很强），但消除检查减少了代码体积，且在不同架构上收益可能不同。更重要的是，BCE 依赖前面的 CSE pass 提供基础——又是链式依赖的一环。查看命令：&lt;code&gt;go build -gcflags=&amp;quot;-d=ssa/check_bce&amp;quot; ./...&lt;/code&gt;&lt;/p&gt;&#xA;&#xA;    &lt;/blockquote&gt;&#xA;&lt;h2 id=&#34;五编译器的盲区&#34;&gt;&lt;a href=&#34;#%e4%ba%94%e7%bc%96%e8%af%91%e5%99%a8%e7%9a%84%e7%9b%b2%e5%8c%ba&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;五、编译器的盲区&#xA;&lt;/h2&gt;&lt;p&gt;编译器也有它看不清的地方——有些是权衡，有些是暂时的限制。&lt;/p&gt;&#xA;&lt;h3 id=&#34;接口手电筒照到黑盒&#34;&gt;&lt;a href=&#34;#%e6%8e%a5%e5%8f%a3%e6%89%8b%e7%94%b5%e7%ad%92%e7%85%a7%e5%88%b0%e9%bb%91%e7%9b%92&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;接口：手电筒照到黑盒&#xA;&lt;/h3&gt;&lt;p&gt;这是最常见的盲区。前面实验已经证明了：当编译器无法确定接口的具体类型时，接口调用无法去虚化，也无法内联。手电筒照到黑盒，看不清里面。&lt;/p&gt;&#xA;&lt;p&gt;但有一个例外：PGO（Profile-Guided Optimization）。Go 1.21 正式支持了 PGO（1.20 为预览版），通过运行时 profile 数据告诉编译器&amp;quot;这个接口调用 99% 走的是 A 类型&amp;quot;，编译器可以据此做推测性去虚化，打开内联机会。Uber 的生产环境实测整体性能提升约 4%（来源：Uber Engineering Blog, 2025年3月），合成基准测试中甚至达到 10%-12%。4% 听不多，但在大规模服务上意味着节省数十台机器。&lt;/p&gt;&#xA;&lt;p&gt;不过 PGO 是另一个话题了，对大多数个人开发者来说，更实际的方法是：&lt;strong&gt;在热路径上避免通过接口调用小函数&lt;/strong&gt;。不是不用接口，是别在每秒百万次调用的循环里用。&lt;/p&gt;&#xA;&lt;h3 id=&#34;泛型手电筒照到毛玻璃&#34;&gt;&lt;a href=&#34;#%e6%b3%9b%e5%9e%8b%e6%89%8b%e7%94%b5%e7%ad%92%e7%85%a7%e5%88%b0%e6%af%9b%e7%8e%bb%e7%92%83&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;泛型：手电筒照到毛玻璃&#xA;&lt;/h3&gt;&lt;p&gt;Go 的泛型目前采用 GC shape stenciling + 字典实现。编译器按 GC 形状（指针类型 vs 值类型）生成多份代码，但同一 GC 形状内的不同具体类型共享代码，通过字典在运行时查找类型信息。手电筒照到毛玻璃——轮廓在，但细节糊了。&lt;/p&gt;&#xA;&lt;p&gt;这意味着编译器在编译期仍看不到每个具体类型的完整信息，很多优化做不了。在 Go 1.26 中，泛型函数的优化确实受限。目前没有公开的改进时间表，但这不是永久限制——GC shape stenciling 本身就是从 Go 1.18 到 1.26 逐步改进的结果。&lt;/p&gt;&#xA;&lt;h3 id=&#34;反射手电筒被关掉了&#34;&gt;&lt;a href=&#34;#%e5%8f%8d%e5%b0%84%e6%89%8b%e7%94%b5%e7%ad%92%e8%a2%ab%e5%85%b3%e6%8e%89%e4%ba%86&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;反射：手电筒被关掉了&#xA;&lt;/h3&gt;&lt;p&gt;反射是编译器最头疼的东西。通过反射调用方法，内联、逃逸优化、去虚化全部失效——手电筒被关掉了。datasea 在《Go反射无法内联、无法逃逸分析、无法被SSA优化》中做了深度拆解，这里不展开。&lt;/p&gt;&#xA;&lt;p&gt;关键不是&amp;quot;不能用反射&amp;quot;，是用在哪——序列化/反序列化的热路径上用反射是灾难，启动初始化时用反射无所谓。&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;概念图：编译器盲区&#34; class=&#34;gallery-image&#34; data-flex-basis=&#34;321px&#34; data-flex-grow=&#34;133&#34; height=&#34;896&#34; loading=&#34;lazy&#34; sizes=&#34;(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px&#34; src=&#34;https://img.wujiachen.com.cn/go-compiler-optimization/ch5-blindspot.png!/watermark/text/5q2i6K+tTGFi/size/20/color/666666/opacity/70/align/southeast&#34; srcset=&#34;https://www.wujiachen.com.cn/ch5-blindspot_11820832496360423714_hu_b10d1c9c7a0934e3.png 800w, https://img.wujiachen.com.cn/go-compiler-optimization/ch5-blindspot.png!/watermark/text/5q2i6K+tTGFi/size/20/color/666666/opacity/70/align/southeast 1200w&#34; width=&#34;1200&#34;&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;六写给编译器看的代码&#34;&gt;&lt;a href=&#34;#%e5%85%ad%e5%86%99%e7%bb%99%e7%bc%96%e8%af%91%e5%99%a8%e7%9c%8b%e7%9a%84%e4%bb%a3%e7%a0%81&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;六、写给编译器看的代码&#xA;&lt;/h2&gt;&lt;p&gt;还记得开头那个凌晨两点的编译器吗？现在你知道它为什么在接口调用前停下了。&lt;/p&gt;&#xA;&lt;p&gt;那怎么写代码，才能让编译器看得懂？&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;原则一：热路径上的小函数，保持简单。&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;函数越简单，内联预算越够用（默认预算 80 个节点，函数里的表达式、控制流都会消耗预算）。一个只有 &lt;code&gt;return a + b&lt;/code&gt; 的函数，编译器闭着眼都能内联。加复杂循环、多重分支，预算就不够了。热路径上的核心计算函数，能短则短。用 &lt;code&gt;-gcflags=&amp;quot;-m -m&amp;quot;&lt;/code&gt; 能看到每个函数的复杂度分数。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;原则二：接口用在大边界，不要用在循环里。&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;接口是 Go 的核心抽象，不是不用。但接口调用的动态分发有代价。在&amp;quot;模块间通信&amp;quot;的大边界用接口完全合理，在每秒百万次的循环内部用接口调用小函数就不合理了。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;原则三：不要为了&amp;quot;灵活性&amp;quot;返回指针。&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;func getX() *int&lt;/code&gt; 看起来比 &lt;code&gt;func getX() int&lt;/code&gt; 更灵活，但编译器必须把 &lt;code&gt;x&lt;/code&gt; 分配到堆上。如果调用方不需要修改返回值，值返回就够了。对于小值类型，返回指针的代价远超那点灵活性。但大 struct 要实测——值拷贝本身有开销，有时候返回指针反而更快。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;最重要的方法论：验证，不要猜。&lt;/strong&gt; 编译器的优化决策不是秘密，一行命令就能看到：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;go build -gcflags&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;-m&amp;#34;&lt;/span&gt; ./... 2&amp;gt;&lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; grep inline&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;go build -gcflags&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;-m&amp;#34;&lt;/span&gt; ./... 2&amp;gt;&lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; grep escape&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;go build -gcflags&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;-d=ssa/check_bce&amp;#34;&lt;/span&gt; ./...&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;不要背规则——Go 编译器每个版本都在变。今天不能内联的函数，下个版本可能就可以了。记住验证方法，比记住一百条优化技巧管用。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;原则五：编译器盲区有替代方案。&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;泛型场景可以用代码生成替代反射，热路径上用具体类型替代接口，必须用反射时限制在初始化阶段。这些不是你写错了代码，是编译器当前的客观限制——承认它，绕过它。&lt;/p&gt;&#xA;&lt;p&gt;明天写代码之前，跑一下 &lt;code&gt;-gcflags=&amp;quot;-m&amp;quot;&lt;/code&gt;，看看凌晨两点的编译器，是不是真的看得懂你的代码。&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;对照图：编译器友好 vs 不友好&#34; class=&#34;gallery-image&#34; data-flex-basis=&#34;321px&#34; data-flex-grow=&#34;133&#34; height=&#34;896&#34; loading=&#34;lazy&#34; sizes=&#34;(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px&#34; src=&#34;https://img.wujiachen.com.cn/go-compiler-optimization/ch6-code.png!/watermark/text/5q2i6K+tTGFi/size/20/color/666666/opacity/70/align/southeast&#34; srcset=&#34;https://www.wujiachen.com.cn/ch6-code_6247728364577681625_hu_f89f76d08b88f0ec.png 800w, https://img.wujiachen.com.cn/go-compiler-optimization/ch6-code.png!/watermark/text/5q2i6K+tTGFi/size/20/color/666666/opacity/70/align/southeast 1200w&#34; width=&#34;1200&#34;&gt;&lt;/p&gt;&#xA;</description>
        </item></channel>
</rss>
