<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Tony Bai &#187; Portability</title>
	<atom:link href="http://tonybai.com/tag/portability/feed/" rel="self" type="application/rss+xml" />
	<link>https://tonybai.com</link>
	<description>一个程序员的心路历程</description>
	<lastBuildDate>Mon, 08 Jun 2026 23:32:23 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
		<item>
		<title>Go 1.27 将默认开启 SIMD for amd64，可移植 SIMD 包提案出炉</title>
		<link>https://tonybai.com/2026/04/29/go-1-27-default-simd-for-amd64-portable-simd-proposal/</link>
		<comments>https://tonybai.com/2026/04/29/go-1-27-default-simd-for-amd64-portable-simd-proposal/#comments</comments>
		<pubDate>Wed, 29 Apr 2026 00:16:43 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[AbstractionLayer]]></category>
		<category><![CDATA[amd64]]></category>
		<category><![CDATA[ArchitectureAgnostic]]></category>
		<category><![CDATA[archsimd]]></category>
		<category><![CDATA[ARM]]></category>
		<category><![CDATA[Assembly]]></category>
		<category><![CDATA[CompilerOptimization]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Go1.27]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[HighPerformance]]></category>
		<category><![CDATA[InstructionSetArchitecture]]></category>
		<category><![CDATA[NEON]]></category>
		<category><![CDATA[ParallelComputing]]></category>
		<category><![CDATA[PerformanceTuning]]></category>
		<category><![CDATA[Portability]]></category>
		<category><![CDATA[Scalability]]></category>
		<category><![CDATA[SIMD]]></category>
		<category><![CDATA[SingleInstructionMultipleData]]></category>
		<category><![CDATA[SVE]]></category>
		<category><![CDATA[Vectorization]]></category>
		<category><![CDATA[wasm]]></category>
		<category><![CDATA[ZeroCost]]></category>
		<category><![CDATA[单指令多数据流]]></category>
		<category><![CDATA[可扩展性]]></category>
		<category><![CDATA[可移植性]]></category>
		<category><![CDATA[向量化]]></category>
		<category><![CDATA[并行计算]]></category>
		<category><![CDATA[性能压榨]]></category>
		<category><![CDATA[抽象层]]></category>
		<category><![CDATA[架构无关]]></category>
		<category><![CDATA[汇编]]></category>
		<category><![CDATA[硬件指令集]]></category>
		<category><![CDATA[编译优化]]></category>
		<category><![CDATA[零成本]]></category>
		<category><![CDATA[高性能]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=6243</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2026/04/29/go-1-27-default-simd-for-amd64-portable-simd-proposal 大家好，我是Tony Bai。 过去十年，Go 语言以其惊人的简洁和强大的并发能力，席卷了整个云原生领域。但在这片繁荣之下，一个尴尬的“阿喀琉斯之踵”，始终困扰着所有追求极致性能的 Gopher： Go 语言，无法像 C++ 或 Rust 那样，原生且优雅地利用现代 CPU 的 SIMD（单指令多数据流）能力。 当你需要处理海量数据（如向量计算、图像处理、加解密）时，手写 Go 代码的性能，往往会被隔壁 C++/Rust 的 SIMD 优化版本，拉开数倍甚至数十倍的差距。为了榨干 CPU 的最后一滴性能，我们不得不去手写那些极其晦涩、难以维护、且无法被 GC 优雅调度的 Go 汇编。 但就在今年年初发布的Go 1.26版本中，这场长达十年的“性能怨念”，终于迎来了终结的曙光。Go 1.26以实验特性形式在AMD64架构上提供了SIMD的支持。 近期，Go 核心团队在官方 GitHub 仓库中，又密集地抛出了一系列重磅提案（#78902, #78979等）。这些提案不仅宣告了在 Go 1.26 中实验性加入的 SIMD 功能大获成功，更进一步宣布： 在即将到来的 Go 1.27 中，simd/archsimd 包将默认开启！同时，一个早已规划好的、架构无关的“可移植（Portable）”SIMD API 也已正式提案！ Go 团队试图用一种极其“Go-like”的优雅方式，为我们揭开 SIMD 这头性能怪兽的封印。 今天，就让我们来拆解这场 [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2026/go-1-27-default-simd-for-amd64-portable-simd-proposal-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2026/04/29/go-1-27-default-simd-for-amd64-portable-simd-proposal">本文永久链接</a> &#8211; https://tonybai.com/2026/04/29/go-1-27-default-simd-for-amd64-portable-simd-proposal</p>
<p>大家好，我是Tony Bai。</p>
<p>过去十年，Go 语言以其惊人的简洁和强大的并发能力，席卷了整个云原生领域。但在这片繁荣之下，一个尴尬的“阿喀琉斯之踵”，始终困扰着所有追求极致性能的 Gopher：</p>
<p><strong>Go 语言，无法像 C++ 或 Rust 那样，原生且优雅地利用现代 CPU 的 SIMD（单指令多数据流）能力。</strong></p>
<p>当你需要处理海量数据（如向量计算、图像处理、加解密）时，手写 Go 代码的性能，往往会被隔壁 C++/Rust 的 SIMD 优化版本，拉开数倍甚至数十倍的差距。为了榨干 CPU 的最后一滴性能，我们不得不去手写那些极其晦涩、难以维护、且无法被 GC 优雅调度的 <a href="https://tonybai.com/2024/07/21/simd-in-go/">Go 汇编</a>。</p>
<p>但就在今年年初发布的<a href="https://tonybai.com/2026/02/14/some-changes-in-go-1-26/">Go 1.26版本</a>中，这场长达十年的“性能怨念”，终于迎来了终结的曙光。Go 1.26以实验特性形式在AMD64架构上提供了<a href="https://tonybai.com/2025/08/22/go-simd-package-preview/">SIMD的支持</a>。</p>
<p>近期，Go 核心团队在官方 GitHub 仓库中，又密集地抛出了一系列重磅提案（#78902, #78979等）。这些提案不仅宣告了在 Go 1.26 中实验性加入的 SIMD 功能大获成功，更进一步宣布： <strong>在即将到来的 Go 1.27 中，simd/archsimd 包将默认开启！同时，一个早已规划好的、架构无关的“可移植（Portable）”SIMD API 也已正式提案！</strong></p>
<p>Go 团队试图用一种极其“Go-like”的优雅方式，为我们揭开 SIMD 这头性能怪兽的封印。</p>
<p>今天，就让我们来拆解这场 Go 语言的“性能下半场”革命，看看 Go 团队到底在下一盘怎样的大棋。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/agentic-software-engineering-qr.png" alt="" /></p>
<h2>Go 的 SIMD 哲学：syscall vs os 的“两层模型”</h2>
<p>要理解 Go 的 SIMD 设计，我们必须先看懂官方在 Issue #73787 中提出的核心哲学——<strong>“两层模型（Two-level approach）”</strong>。</p>
<p>Go 团队清醒地认识到，SIMD 的世界充满了矛盾：</p>
<ul>
<li><strong>底层</strong>：硬件指令集是<strong>非可移植的（Non-portable）</strong>。AMD64 上的 AVX512、ARM 上的 NEON/SVE、Wasm 里的 SIMD，它们的向量宽度、指令名称、甚至掩码（Mask）的表示方式都截然不同。</li>
<li><strong>上层</strong>：Go 语言的核心魅力，恰恰是它的<strong>可移植性（Portability）</strong>。一份代码，处处运行。</li>
</ul>
<p>如何调和这个矛盾？Go 团队从标准库中 syscall 和 os 包的关系里，找到了灵感。</p>
<p><strong>第一层：simd/archsimd —— 你的“syscall”</strong></p>
<p>这一层，是<strong>架构绑定的、低级别的</strong>。它将 CPU 的 SIMD 指令，近乎一对一地封装成 Go 的函数。比如 VPADDD 指令，就对应着 Uint32x4.Add()。</p>
<p>这一层追求的是极致的表达力和与硬件的零距离。它就是为那些需要手写汇编的“性能狂人”准备的。如果你想调用某个 AVX512 的独有指令，来这里就对了。</p>
<p><strong>第二层：simd —— 你的“os”</strong></p>
<p>这一层，将是<strong>架构无关的、高级别的</strong>。它会定义一套通用的、不依赖特定向量宽度的向量类型（如 simd.Float32s），以及一套通用的操作（如 Add, Mul）。</p>
<p>当你写下 a.Add(b) 时，编译器会根据你当前的编译目标（GOARCH），自动将其翻译成最高效的底层 archsimd 指令。</p>
<p>这一层追求的是极致的可移植性和易用性。对于 99% 的开发者来说，你只需要和这一层打交道。</p>
<h2>硬核拆解：Go 1.27 即将转正的 simd/archsimd</h2>
<p>在 Go 1.26 的 GOEXPERIMENT=simd 实验成功后，Go 团队在 Issue #78979 中正式提案，将 simd/archsimd for AMD64 在 Go 1.27 中<strong>默认开启</strong>！</p>
<p>让我们来一睹这把“屠龙刀”的真容：</p>
<p><strong>1. 强类型的向量定义</strong></p>
<p>告别 unsafe.Pointer 和丑陋的字节数组！archsimd 为不同位宽和数据类型，定义了极其清晰的结构体：</p>
<pre><code class="go">// 128位，4个 uint32
type Uint32x4 struct { a0, a1, a2, a3 uint32 }
// 256位，8个 float32
type Float32x8 struct { /* ... */ }
</code></pre>
<p><strong>2. 易于理解的方法链</strong></p>
<p>所有的 SIMD 操作，都被设计成了易于阅读和链式调用的方法。注释里甚至贴心地标出了对应的汇编指令。</p>
<pre><code class="go">// Add each element of two vectors.
//
// Equivalent to x86 instruction VPADDD.
func (Uint32x4) Add(Uint32x4) Uint32x4
</code></pre>
<p><strong>3. 抽象的掩码（Mask）类型</strong></p>
<p>如何处理不同架构下千奇百怪的掩码，是 SIMD API 设计中最头疼的问题。Go 团队选择了用一个不透明的 Mask 类型来屏蔽底层差异，让编译器自己去选择最高效的实现（K-register 还是 Vector-register）。</p>
<h2>Go的野心：可移植的 simd 包提案出炉</h2>
<p>如果说 archsimd 只是让 Go “追平”了 C++/Rust，那么 <strong>Issue #78902</strong> 中提出的高级 simd 包，则真正展现了 Go 语言的“野心”——<strong>在可移植性上，超越所有前辈。</strong></p>
<p>在这个提案中，dr2chase 描绘了一个极其诱人的未来。你将可以这样写代码：</p>
<pre><code class="go">// 一个 inner product 示例
func ip(x, y []float32) float32 {
    var a simd.Float32s // 注意！这里没有指定位宽！
    var i int
    // a.Len() 会在运行时自动返回当前 CPU 支持的最佳向量宽度
    for i = 0; i &lt; len(x)-a.Len()+1; i += a.Len() {
        u := simd.LoadFloat32Slice(x[i : i+a.Len()])
        v := simd.LoadFloat32Slice(y[i : i+a.Len()])
        a = a.Add(u.Mul(v))
    }
    // ... 处理剩余的尾部数据
    return sum(a) // 水平求和
}
</code></pre>
<p>sum函数在amd64平台的具体实现：</p>
<pre><code>//go:build amd64
package main
import (
    "simd"
    "simd/archsimd"
)

func sum(x simd.Float32s) float32 {
    switch a := x.ToArch().(type) {
    case archsimd.Float32x8:
        a = a.AddPairsGrouped(a)
        a = a.AddPairsGrouped(a)
        return a.GetLo().GetElem(0) + a.GetHi().GetElem(0)
    case archsimd.Float32x16:
        s := make([]float32, a.Len())
        a.StoreSlice(s)
        var r float32
        for _, e := range s {
            r += e
        }
        return r
    case archsimd.Float32x4:
        s := make([]float32, a.Len())
        a.StoreSlice(s)
        var r float32
        for _, e := range s {
            r += e
        }
        return r
    }
    panic("not a known type")
}
</code></pre>
<p><strong>看懂了吗？</strong></p>
<p>你只需要写一份代码，把它扔到一台只支持 AVX2 的机器上，a.Len() 会返回 8；把它扔到一台支持 AVX512 的机器上，a.Len() 会自动变成 16！</p>
<p>编译器会自动为你生成多个版本的代码，并在运行时动态选择最优路径。这彻底将开发者从“为不同 CPU 手写不同优化版本”的地狱中解放了出来。</p>
<h2>神仙打架：一场关于“命名哲学”的激烈辩论</h2>
<p>在 Issue #73787 的评论区，一场关于 SIMD 函数命名哲学的“神仙打架”，精彩绝伦。</p>
<ul>
<li>
<p><strong>以 Ian Lance Taylor 为首的“专家派”认为</strong>：</p>
<blockquote>
<p>“应该直接使用 VPADDD 这样的汇编指令名。这对于专家来说更友好，他们不需要在脑子里多做一次‘Go 风格名称’到‘Intel 手册名称’的翻译。”</p>
</blockquote>
</li>
<li>
<p><strong>以 Cherry Mui 为首的“可读性派”则坚决反对</strong>：</p>
<blockquote>
<p>“代码的读者，远比代码的作者多。一个普通开发者能轻易猜出 Add 的意思，但绝对猜不出 VPADDD 是什么鬼。我们应该为读者优化，而不是为专家。”</p>
</blockquote>
</li>
</ul>
<p>最终，“可读性派”胜出。这也再次印证了 Go 语言一以贯之的设计哲学：<strong>明确性与可读性，永远高于一切。</strong></p>
<h2>小结：Go 语言的“性能下半场”</h2>
<p>SIMD 的正式入场，标志着 Go 语言的演进，正在进入一个全新的阶段。</p>
<p>如果说过去十年，Go 靠着“并发”和“简洁”赢得了云原生的上半场；那么在未来十年，它将靠着这套兼具“优雅可移植”与“极致性能”的 SIMD 工具链，去硬刚 AI、数据科学、游戏引擎这些性能深水区（如果后续新版本的 AI 学会了如何使用这些新增SIMD特性）。</p>
<p>Go 团队没有选择像 C++ 那样直接暴露几百个晦涩的 Intrinsics，也没有像 Rust 那样在稳定性和表达力之间反复纠结。</p>
<p>它用一套极其深思熟虑的“两层模型”，试图在这场性能的终局之战中，走出一条属于自己的路。</p>
<p><strong>Go 1.27，将是我们所有 Gopher 重新认识这门语言的开始。</strong></p>
<p>那扇通往极致性能的大门，正在被缓缓推开。你，准备好了吗？</p>
<p>资料链接：</p>
<ul>
<li>https://github.com/golang/go/issues/73787</li>
<li>https://github.com/golang/go/issues/78979</li>
<li>https://github.com/golang/go/issues/78902</li>
</ul>
<hr />
<p><strong>今日互动探讨：</strong></p>
<p>在你的日常工作中，有哪些场景是目前 Go 语言性能的瓶颈，让你极其渴望 SIMD 的加持？对于 Go 团队设计的这套“两层 SIMD API”，你是更看好它的“可移植性”还是“性能潜力”？</p>
<p>欢迎在评论区分享你的看法！</p>
<hr />
<p>还在为写 Agent 框架频频死循环、上下文爆炸而束手无策？我的新专栏 <strong>《<a href="http://gk.link/a/12IzL">从0 开始构建 Agent Harness</a>》</strong> 将带你：</p>
<ul>
<li>抛弃臃肿框架，回归“驾驭工程 (Harness Engineering)”的第一性原理</li>
<li>用 Go 语言手写 ReAct 循环、并发拦截与上下文压缩引擎等，复刻极简OpenClaw</li>
<li>构建坚不可摧的 Safety Middleware 与飞书人工审批防线</li>
<li>在底层实现 Token 成本审计、链路追踪与自动化跑分评估</li>
<li>从“调包侠”进化为掌控大模型边界的“AI 操作系统架构师”</li>
</ul>
<p>扫描下方二维码，开启从 0 开始构建Agent Harness 的实战之旅。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/build-agent-harness-from-scratch-qr.png" alt="" /></p>
<hr />
<p><strong>原「Gopher部落」已重装升级为「Go &amp; AI 精进营」知识星球，快来加入星球，开启你的技术跃迁之旅吧！</strong></p>
<p>我们致力于打造一个高品质的 <strong>Go 语言深度学习</strong> 与 <strong>AI 应用探索</strong> 平台。在这里，你将获得：</p>
<ul>
<li><strong>体系化 Go 核心进阶内容:</strong> 深入「Go原理课」、「Go进阶课」、「Go避坑课」等独家深度专栏，夯实你的 Go 内功。</li>
<li><strong>前沿 Go+AI 实战赋能:</strong> 紧跟时代步伐，学习「Go+AI应用实战」、「Agent开发实战课」、「Agentic软件工程课」、「Claude Code开发工作流实战课」、「OpenClaw实战分享」等，掌握 AI 时代新技能。 </li>
<li><strong>星主 Tony Bai 亲自答疑:</strong> 遇到难题？星主第一时间为你深度解析，扫清学习障碍。</li>
<li><strong>高活跃 Gopher 交流圈:</strong> 与众多优秀 Gopher 分享心得、讨论技术，碰撞思想火花。</li>
<li><strong>独家资源与内容首发:</strong> 技术文章、课程更新、精选资源，第一时间触达。</li>
</ul>
<p>衷心希望「Go &amp; AI 精进营」能成为你学习、进步、交流的港湾。让我们在此相聚，享受技术精进的快乐！欢迎你的加入！</p>
<p><img src="http://image.tonybai.com/img/tonybai/gopher-and-ai-tribe-zsxq-small-card.jpg" alt="img{512x368}" /></p>
<hr />
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求，请扫描下方公众号二维码，与我私信联系。</p>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p style='text-align:left'>&copy; 2026, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2026/04/29/go-1-27-default-simd-for-amd64-portable-simd-proposal/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Go 的“浮点数陷阱”将被填平：浮点转整数即将在所有平台上行为一致</title>
		<link>https://tonybai.com/2026/01/11/proposal-float-to-int-conversions-should-saturate-on-overflow/</link>
		<comments>https://tonybai.com/2026/01/11/proposal-float-to-int-conversions-should-saturate-on-overflow/#comments</comments>
		<pubDate>Sat, 10 Jan 2026 23:31:45 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[amd64]]></category>
		<category><![CDATA[arm64]]></category>
		<category><![CDATA[Compiler]]></category>
		<category><![CDATA[consistency]]></category>
		<category><![CDATA[Conversion]]></category>
		<category><![CDATA[CrossPlatform]]></category>
		<category><![CDATA[DavidChase]]></category>
		<category><![CDATA[exception]]></category>
		<category><![CDATA[FloatingPoint]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[go1.26]]></category>
		<category><![CDATA[Go1.27]]></category>
		<category><![CDATA[Go1.28]]></category>
		<category><![CDATA[GOEXPERIMENT]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[HardwareDifference]]></category>
		<category><![CDATA[IanLanceTaylor]]></category>
		<category><![CDATA[ImplementationDefined]]></category>
		<category><![CDATA[InstructionSet]]></category>
		<category><![CDATA[Integer]]></category>
		<category><![CDATA[NaN]]></category>
		<category><![CDATA[Overflow]]></category>
		<category><![CDATA[PerfectPortability]]></category>
		<category><![CDATA[Performance]]></category>
		<category><![CDATA[Portability]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[RISCV]]></category>
		<category><![CDATA[SaturatingConversion]]></category>
		<category><![CDATA[一致性]]></category>
		<category><![CDATA[可移植性]]></category>
		<category><![CDATA[完美可移植性]]></category>
		<category><![CDATA[实现定义]]></category>
		<category><![CDATA[异常]]></category>
		<category><![CDATA[性能]]></category>
		<category><![CDATA[指令集]]></category>
		<category><![CDATA[整数]]></category>
		<category><![CDATA[浮点数]]></category>
		<category><![CDATA[溢出]]></category>
		<category><![CDATA[硬件差异]]></category>
		<category><![CDATA[程序员]]></category>
		<category><![CDATA[编译器]]></category>
		<category><![CDATA[跨平台]]></category>
		<category><![CDATA[转换]]></category>
		<category><![CDATA[饱和转换]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=5703</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2026/01/11/proposal-float-to-int-conversions-should-saturate-on-overflow 大家好，我是Tony Bai。 你是否知道，同一行简单的代码 int64(myFloat)，在 Intel (amd64) 机器上可能返回一个巨大的负数，而在 ARM64 机器上却可能返回最大正整数？ 在 Go 语言中，浮点数到整数的转换溢出行为长期以来一直属于“实现定义”(implementation-dependent) 的灰色地带。这意味着，代码的运行结果竟然取决于你底层的 CPU 架构。这种不确定性，一直是跨平台开发中一个难以察觉的隐形地雷。 2025年末，Go 编译器团队核心成员 David Chase 提交了一份提案（#76264），旨在彻底终结这种混乱。该提案计划在未来的 Go 版本中，强制规定所有平台上的浮点转整数必须是“饱和”的 (saturating)，从而实现真正的全平台行为一致。 痛点：薛定谔的转换结果 在现有的 Go 规范下，如果你尝试将一个超出目标整数范围的浮点数（例如 1e100）转换为 int64，结果是未定义的。 让我们看看这有多疯狂。假设我们有以下代码： var f float64 = 1e100 // 一个巨大的数 var i int64 = int64(f) fmt.Println(i) 这段代码在不同架构下的运行结果截然不同： ARM64, RISC-V: 返回 9223372036854775807 (MAX_INT64)。这是“饱和”行为，即卡在最大值。 AMD64 (x86-64): 返回 [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2026/proposal-float-to-int-conversions-should-saturate-on-overflow-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2026/01/11/proposal-float-to-int-conversions-should-saturate-on-overflow">本文永久链接</a> &#8211; https://tonybai.com/2026/01/11/proposal-float-to-int-conversions-should-saturate-on-overflow</p>
<p>大家好，我是Tony Bai。</p>
<p>你是否知道，同一行简单的代码 int64(myFloat)，在 Intel (amd64) 机器上可能返回一个巨大的负数，而在 ARM64 机器上却可能返回最大正整数？</p>
<p>在 Go 语言中，浮点数到整数的转换溢出行为长期以来一直属于“实现定义”(implementation-dependent) 的灰色地带。这意味着，代码的运行结果竟然取决于你底层的 CPU 架构。这种不确定性，一直是跨平台开发中一个难以察觉的隐形地雷。</p>
<p>2025年末，Go 编译器团队核心成员 David Chase 提交了一份提案（<a href="https://github.com/golang/go/issues/76264">#76264</a>），旨在彻底终结这种混乱。该提案计划在未来的 Go 版本中，<strong>强制规定所有平台上的浮点转整数必须是“饱和”的 (saturating)</strong>，从而实现真正的全平台行为一致。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/distributed-system-guide-qr.png" alt="img{512x368}" /></p>
<h2>痛点：薛定谔的转换结果</h2>
<p>在现有的 Go 规范下，如果你尝试将一个超出目标整数范围的浮点数（例如 1e100）转换为 int64，结果是未定义的。</p>
<p>让我们看看这有多疯狂。假设我们有以下代码：</p>
<pre><code class="go">var f float64 = 1e100 // 一个巨大的数
var i int64 = int64(f)
fmt.Println(i)
</code></pre>
<p>这段代码在不同架构下的运行结果截然不同：</p>
<ul>
<li><strong>ARM64, RISC-V</strong>: 返回 9223372036854775807 (<strong>MAX_INT64</strong>)。这是“饱和”行为，即卡在最大值。</li>
<li><strong>AMD64 (x86-64)</strong>: 返回 -9223372036854775808 (<strong>MIN_INT64</strong>)。这是一个令人困惑的溢出结果。</li>
<li><strong>WASM</strong>: 行为又不一样&#8230;</li>
</ul>
<p>更糟糕的是 NaN (Not a Number) 的转换：</p>
<pre><code class="go">var j int64 = int64(math.NaN())
fmt.Println(j)
</code></pre>
<ul>
<li><strong>ARM64</strong>: 返回 0。</li>
<li><strong>AMD64</strong>: 返回 <strong>MIN_INT64</strong>。</li>
<li><strong>RISC-V</strong>: 返回 <strong>MAX_INT64</strong>。</li>
</ul>
<p>这种不一致性不仅仅是理论问题，它已经导致了准标准库 x/time/rate 中的真实 Bug (<a href="https://github.com/golang/go/issues/71154">#71154</a>)。当你的代码逻辑依赖于转换结果的正负号来做判断时（例如 if i > 0），这种硬件差异就是致命的。</p>
<h2>解决方案：拥抱“饱和转换”</h2>
<p>David Chase 的提案非常直接：<strong>统一行为，拥抱饱和。</strong></p>
<p>所谓“饱和转换”，是指当浮点数超出目标整数的表示范围时，结果应该被“钳制”在目标类型的最大值或最小值，而不是发生回绕(wraparound)或产生随机值。</p>
<p>具体规则如下：</p>
<ol>
<li><strong>正溢出</strong> -> 返回目标类型的 <strong>最大值</strong> (MaxInt)。</li>
<li><strong>负溢出</strong> -> 返回目标类型的 <strong>最小值</strong> (MinInt)。</li>
<li><strong>NaN</strong> -> 返回 <strong>0</strong> (或归一化为 0)。</li>
</ol>
<p>这一改变将使得 Go 代码在任何 CPU 架构上都表现出完全一致的逻辑，彻底消除了这类可移植性隐患。</p>
<h2>深层权衡：一致性 vs. 性能</h2>
<p>为什么 Go 以前不这么做？核心原因在于<strong>性能成本</strong>。</p>
<p>在 ARM64 和 RISC-V 等现代架构上，硬件指令集（如 FCVT）原生支持饱和转换，因此这样做几乎没有额外开销。</p>
<p>然而，<strong>AMD64 (x86-64) 是个“异类”</strong>。它的 CVTTSD2SQ 指令在溢出时不仅返回一个特殊的“不定值”（通常是 MinInt），还会触发浮点异常。为了在 AMD64 上模拟出“饱和”行为，编译器必须插入额外的检查代码：</p>
<pre><code class="go">// 模拟代码逻辑：AMD64 上的额外开销
result = int64(x)
if result == MIN_INT64 { // 可能溢出了
    if x &gt; 0 {
        result = MAX_INT64 // 正溢出修正
    } else if !(x &lt; 0) {
        result = 0         // NaN 修正
    }
}
</code></pre>
<p>Go 核心团队成员 Ian Lance Taylor 在评论中指出，我们必须权衡：<strong>为了消除这种不一致性，值得让 AMD64 上的转换操作变慢吗？</strong></p>
<p>提案作者 David Chase 的回应是：<strong>值得。</strong> 与 FMA (融合乘加) 指令带来的微小精度差异不同，浮点转整数的差异往往是<strong>正负号级别</strong>的（MaxInt vs MinInt），这直接决定了代码逻辑的走向（循环是否执行、条件是否满足）。这种差异带来的 Bug 极其隐蔽且难以调试，其代价远超那几条指令的性能损耗。</p>
<h2>实施计划：温和的演进</h2>
<p>为了避免生态系统的剧烈震荡，提案建议采用分阶段的落地策略：</p>
<ul>
<li><strong>Go 1.26</strong>: 引入 GOEXPERIMENT 标志，允许开发者尝鲜并测试影响。</li>
<li><strong>Go 1.27</strong>: 将其设为默认的实现行为。</li>
<li><strong>Go 1.28</strong>: 正式修改 Go 语言规范 (Spec)，将其确立为标准。</li>
</ul>
<blockquote>
<p>注：Go 1.26当前已经功能冻结，<a href="https://github.com/golang/go/issues/33892#issuecomment-3721268260">该提案依然处于Go语言规范变更审查委员会的讨论状态中</a>，因此即便逻辑，其实际落地时间表也会顺延。</p>
</blockquote>
<h2>小结：Go 向“完美可移植性”迈出的重要一步</h2>
<p>Dr Chase的这个提案不仅是对一个技术细节的修正，更是 Go 语言设计哲学的一次体现：<strong>在工程实践中，可预测性和可移植性往往优于特定平台上的极致微优化。</strong></p>
<p>如果该提案通过，未来的 Gopher 们将不再需要担心底层的 CPU 是 Intel 还是 ARM，int64(NaN) 永远是 0，int64(Inf) 永远是 MaxInt64。这，才是我们想要的“Write Once, Run Anywhere”。</p>
<blockquote>
<p>注：目前Dr Chase也在努力弥合amd64下的性能差距。</p>
</blockquote>
<p>资料链接：https://github.com/golang/go/issues/76264</p>
<hr />
<p><strong>你的跨平台“血泪史”</strong></p>
<p>跨平台开发中的“未定义行为”往往是最难调试的 Bug。<strong>在你的开发生涯中，是否也遇到过因为 CPU 架构或操作系统差异而导致的诡异问题？你支持为了“一致性”而牺牲一点点 AMD64 上的性能吗？</strong></p>
<p><strong>欢迎在评论区分享你的踩坑经历或对提案的看法！</strong> 让我们一起见证 Go 语言的进化。</p>
<p><strong>如果这篇文章让你对底层原理有了新的认识，别忘了点个【赞】和【在看】，并转发给你的硬核伙伴！</strong></p>
<hr />
<p>还在为“复制粘贴喂AI”而烦恼？我的新专栏 <strong>《<a href="http://gk.link/a/12EPd">AI原生开发工作流实战</a>》</strong> 将带你：</p>
<ul>
<li>告别低效，重塑开发范式</li>
<li>驾驭AI Agent(Claude Code)，实现工作流自动化</li>
<li>从“AI使用者”进化为规范驱动开发的“工作流指挥家”</li>
</ul>
<p>扫描下方二维码，开启你的AI原生开发之旅。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/ai-native-dev-workflow-qr.png" alt="" /></p>
<hr />
<p>你的Go技能，是否也卡在了“熟练”到“精通”的瓶颈期？</p>
<ul>
<li>想写出更地道、更健壮的Go代码，却总在细节上踩坑？</li>
<li>渴望提升软件设计能力，驾驭复杂Go项目却缺乏章法？</li>
<li>想打造生产级的Go服务，却在工程化实践中屡屡受挫？</li>
</ul>
<p>继《<a href="http://gk.link/a/10AVZ">Go语言第一课</a>》后，我的《<a href="http://gk.link/a/12yGY">Go语言进阶课</a>》终于在极客时间与大家见面了！</p>
<p>我的全新极客时间专栏 《<a href="http://gk.link/a/12yGY">Tony Bai·Go语言进阶课</a>》就是为这样的你量身打造！30+讲硬核内容，带你夯实语法认知，提升设计思维，锻造工程实践能力，更有实战项目串讲。</p>
<p>目标只有一个：助你完成从“Go熟练工”到“Go专家”的蜕变！ 现在就加入，让你的Go技能再上一个新台阶！</p>
<p><img src="https://tonybai.com/wp-content/uploads/course-card/iamtonybai-banner-2.gif" alt="" /></p>
<hr />
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求，请扫描下方公众号二维码，与我私信联系。</p>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p style='text-align:left'>&copy; 2026, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2026/01/11/proposal-float-to-int-conversions-should-saturate-on-overflow/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>也谈Go的可移植性</title>
		<link>https://tonybai.com/2017/06/27/an-intro-about-go-portability/</link>
		<comments>https://tonybai.com/2017/06/27/an-intro-about-go-portability/#comments</comments>
		<pubDate>Tue, 27 Jun 2017 13:42:56 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[amd64]]></category>
		<category><![CDATA[Cgo]]></category>
		<category><![CDATA[CGO_ENABLED]]></category>
		<category><![CDATA[Concurrency]]></category>
		<category><![CDATA[Darwin]]></category>
		<category><![CDATA[Glibc]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[goroutine]]></category>
		<category><![CDATA[ldd]]></category>
		<category><![CDATA[libc]]></category>
		<category><![CDATA[link]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[nm]]></category>
		<category><![CDATA[otool]]></category>
		<category><![CDATA[Portability]]></category>
		<category><![CDATA[Pthread]]></category>
		<category><![CDATA[runtime]]></category>
		<category><![CDATA[stdlib]]></category>
		<category><![CDATA[syscall]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<category><![CDATA[Windows]]></category>
		<category><![CDATA[动态链接库]]></category>
		<category><![CDATA[可移植性]]></category>
		<category><![CDATA[操作系统]]></category>
		<category><![CDATA[标准库]]></category>
		<category><![CDATA[系统调用]]></category>
		<category><![CDATA[运行时]]></category>
		<category><![CDATA[静态连接]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=2353</guid>
		<description><![CDATA[Go有很多优点，比如：简单、原生支持并发等，而不错的可移植性也是Go被广大程序员接纳的重要因素之一。但你知道为什么Go语言拥有很好的平台可移植性吗？本着“知其然，亦要知其所以然”的精神，本文我们就来探究一下Go良好可移植性背后的原理。 一、Go的可移植性 说到一门编程语言可移植性，我们一般从下面两个方面考量： 语言自身被移植到不同平台的容易程度； 通过这种语言编译出来的应用程序对平台的适应性。 在Go 1.7及以后版本中，我们可以通过下面命令查看Go支持OS和平台列表： $go tool dist list android/386 android/amd64 android/arm android/arm64 darwin/386 darwin/amd64 darwin/arm darwin/arm64 dragonfly/amd64 freebsd/386 freebsd/amd64 freebsd/arm linux/386 linux/amd64 linux/arm linux/arm64 linux/mips linux/mips64 linux/mips64le linux/mipsle linux/ppc64 linux/ppc64le linux/s390x nacl/386 nacl/amd64p32 nacl/arm netbsd/386 netbsd/amd64 netbsd/arm openbsd/386 openbsd/amd64 openbsd/arm plan9/386 plan9/amd64 plan9/arm solaris/amd64 windows/386 windows/amd64 从上述列表我们可以看出：从linux/arm64的嵌入式系统到linux/s390x的大型机系统，再到Windows、linux和darwin(mac)这样的主流操作系统、amd64、386这样的主流处理器体系，Go对各种平台和操作系统的支持不可谓不广泛。 Go官方似乎没有给出明确的porting guide，关于将Go语言porting到其他平台上的内容更多是在golang-dev这样的小圈子中讨论的事情。但就Go语言这么短的时间就能很好的支持这么多平台来看，Go的porting还是相对easy的。从个人对Go的了解来看，这一定程度上得益于Go独立实现了runtime。 runtime是支撑程序运行的基础。我们最熟悉的莫过于libc（C运行时），它是目前主流操作系统上应用最普遍的运行时，通常以动态链接库的形式(比如：/lib/x86_64-linux-gnu/libc.so.6)随着系统一并发布，它的功能大致有如下几个： 提供基础库函数调用，比如：strncpy； 封装syscall（注:syscall是操作系统提供的API口，当用户层进行系统调用时，代码会trap(陷入)到内核层面执行），并提供同语言的库函数调用，比如：malloc、fread等； [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://tonybai.com/tag/go">Go</a>有很多优点，比如：<a href="http://tonybai.com/2017/04/20/go-coding-in-go-way/">简单</a>、<a href="http://tonybai.com/2017/06/23/an-intro-about-goroutine-scheduler/">原生支持并发</a>等，而不错的<a href="https://en.wikipedia.org/wiki/Software_portability">可移植性</a>也是Go被广大程序员接纳的重要因素之一。但你知道为什么Go语言拥有很好的平台可移植性吗？本着“知其然，亦要知其所以然”的精神，本文我们就来探究一下Go良好可移植性背后的原理。</p>
<h2>一、Go的可移植性</h2>
<p>说到一门编程语言可移植性，我们一般从下面两个方面考量：</p>
<ul>
<li>语言自身被移植到不同平台的容易程度；</li>
<li>通过这种语言编译出来的应用程序对平台的适应性。</li>
</ul>
<p>在<a href="http://tonybai.com/2016/06/21/some-changes-in-go-1-7/">Go 1.7</a>及以后版本中，我们可以通过下面命令查看Go支持OS和平台列表：</p>
<pre><code>$go tool dist list
android/386
android/amd64
android/arm
android/arm64
darwin/386
darwin/amd64
darwin/arm
darwin/arm64
dragonfly/amd64
freebsd/386
freebsd/amd64
freebsd/arm
linux/386
linux/amd64
linux/arm
linux/arm64
linux/mips
linux/mips64
linux/mips64le
linux/mipsle
linux/ppc64
linux/ppc64le
linux/s390x
nacl/386
nacl/amd64p32
nacl/arm
netbsd/386
netbsd/amd64
netbsd/arm
openbsd/386
openbsd/amd64
openbsd/arm
plan9/386
plan9/amd64
plan9/arm
solaris/amd64
windows/386
windows/amd64
</code></pre>
<p>从上述列表我们可以看出：从<strong>linux/arm64</strong>的嵌入式系统到<strong>linux/s390x</strong>的大型机系统，再到Windows、<a href="http://tonybai.com/tag/ubuntu">linux</a>和darwin(mac)这样的主流操作系统、amd64、386这样的主流处理器体系，Go对各种平台和操作系统的支持不可谓不广泛。</p>
<p>Go官方似乎没有给出明确的porting guide，关于将Go语言porting到其他平台上的内容更多是在<a href="https://groups.google.com/forum/#!forum/golang-dev">golang-dev</a>这样的小圈子中讨论的事情。但就Go语言这么短的时间就能很好的支持这么多平台来看，Go的porting还是相对easy的。从个人对Go的了解来看，这一定程度上得益于Go独立实现了runtime。</p>
<p><img src="http://tonybai.com/wp-content/uploads/go-runtime-vs-c-runtime.png" alt="img{512x368}" /></p>
<p>runtime是支撑程序运行的基础。我们最熟悉的莫过于libc（C运行时），它是目前主流操作系统上应用最普遍的运行时，通常以<a href="http://tonybai.com/2010/12/13/also-talk-about-shared-library/">动态链接库</a>的形式(比如：/lib/x86_64-linux-gnu/libc.so.6)随着系统一并发布，它的功能大致有如下几个：</p>
<ul>
<li>提供基础库函数调用，比如：<a href="http://tonybai.com/2009/04/15/glibc-strncpy-source-analysis/">strncpy</a>；</li>
<li>封装syscall（注:syscall是操作系统提供的API口，当用户层进行系统调用时，代码会trap(陷入)到内核层面执行），并提供同语言的库函数调用，比如：malloc、fread等；</li>
<li>提供程序启动入口函数，比如：linux下的__libc_start_main。</li>
</ul>
<p><a href="http://tonybai.com/2006/07/08/plauger-c-standard-lib-assert-header/">libc</a>等c runtime lib是很早以前就已经实现的了，甚至有些老旧的libc还是单线程的。一些从事c/c++开发多年的程序员早年估计都有过这样的经历：那就是链接runtime库时甚至需要选择链接支持多线程的库还是只支持单线程的库。除此之外，c runtime的版本也参差不齐。这样的c runtime状况完全不能满足go语言自身的需求；另外Go的目标之一是原生支持并发，并使用<a href="http://tonybai.com/2017/06/23/an-intro-about-goroutine-scheduler/">goroutine模型</a>，c runtime对此是无能为力的，因为c runtime本身是基于线程模型的。综合以上因素，Go自己实现了runtime，并封装了syscall，为不同平台上的go user level代码提供封装完成的、统一的go标准库；同时Go runtime实现了对goroutine模型的支持。</p>
<p>独立实现的go runtime层将Go user-level code与OS syscall解耦，把Go porting到一个新平台时，将runtime与新平台的syscall对接即可(当然porting工作不仅仅只有这些)；同时，runtime层的实现基本摆脱了Go程序对libc的依赖，这样静态编译的Go程序具有很好的平台适应性。比如：一个compiled for linux amd64的Go程序可以很好的运行于不同linux发行版（centos、ubuntu）下。</p>
<blockquote>
<p>以下测试试验环境为:darwin amd64 <a href="http://tonybai.com/2017/02/03/some-changes-in-go-1-8/">Go 1.8</a>。</p>
</blockquote>
<h2>二、默认”静态链接”的Go程序</h2>
<p>我们先来写两个程序：hello.c和hello.go，它们完成的功能都差不多，在stdout上输出一行文字：</p>
<pre><code>//hello.c
#include &lt;stdio.h&gt;

int main() {
        printf("%s\n", "hello, portable c!");
        return 0;
}

//hello.go
package main

import "fmt"

func main() {
    fmt.Println("hello, portable go!")
}

</code></pre>
<p>我们采用“默认”方式分别编译以下两个程序：</p>
<pre><code>$cc -o helloc hello.c
$go build -o hellogo hello.go

$ls -l
-rwxr-xr-x    1 tony  staff     8496  6 27 14:18 helloc*
-rwxr-xr-x    1 tony  staff  1628192  6 27 14:18 hellogo*
</code></pre>
<p>从编译后的两个文件helloc和hellogo的size上我们可以看到hellogo相比于helloc简直就是“巨人”般的存在，其size近helloc的200倍。略微学过一些Go的人都知道，这是因为hellogo中包含了必需的go runtime。我们通过otool工具(linux上可以用ldd)查看一下两个文件的对外部动态库的依赖情况：</p>
<pre><code>$otool -L helloc
helloc:
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1197.1.1)
$otool -L hellogo
hellogo:

</code></pre>
<p>通过otool输出，我们可以看到hellogo并不依赖任何外部库，我们将hellog这个二进制文件copy到任何一个mac amd64的平台上，均可以运行起来。而helloc则依赖外部的动态库:/usr/lib/libSystem.B.dylib，而libSystem.B.dylib这个动态库还有其他依赖。我们通过nm工具可以查看到helloc具体是哪个函数符号需要由外部动态库提供：</p>
<pre><code>$nm helloc
0000000100000000 T __mh_execute_header
0000000100000f30 T _main
                 U _printf
                 U dyld_stub_binder
</code></pre>
<p>可以看到：_printf和dyld_stub_binder两个符号是未定义的(对应的前缀符号是U)。如果对hellog使用nm，你会看到大量符号输出，但没有未定义的符号。</p>
<pre><code>$nm hellogo
00000000010bb278 s $f64.3eb0000000000000
00000000010bb280 s $f64.3fd0000000000000
00000000010bb288 s $f64.3fe0000000000000
00000000010bb290 s $f64.3fee666666666666
00000000010bb298 s $f64.3ff0000000000000
00000000010bb2a0 s $f64.4014000000000000
00000000010bb2a8 s $f64.4024000000000000
00000000010bb2b0 s $f64.403a000000000000
00000000010bb2b8 s $f64.4059000000000000
00000000010bb2c0 s $f64.43e0000000000000
00000000010bb2c8 s $f64.8000000000000000
00000000010bb2d0 s $f64.bfe62e42fefa39ef
000000000110af40 b __cgo_init
000000000110af48 b __cgo_notify_runtime_init_done
000000000110af50 b __cgo_thread_start
000000000104d1e0 t __rt0_amd64_darwin
000000000104a0f0 t _callRet
000000000104b580 t _gosave
000000000104d200 T _main
00000000010bbb20 s _masks
000000000104d370 t _nanotime
000000000104b7a0 t _setg_gcc
00000000010bbc20 s _shifts
0000000001051840 t errors.(*errorString).Error
00000000010517a0 t errors.New
.... ...
0000000001065160 t type..hash.time.Time
0000000001064f70 t type..hash.time.zone
00000000010650a0 t type..hash.time.zoneTrans
0000000001051860 t unicode/utf8.DecodeRuneInString
0000000001051a80 t unicode/utf8.EncodeRune
0000000001051bd0 t unicode/utf8.RuneCount
0000000001051d10 t unicode/utf8.RuneCountInString
0000000001107080 s unicode/utf8.acceptRanges
00000000011079e0 s unicode/utf8.first

$nm hellogo|grep " U "

</code></pre>
<p>Go将所有运行需要的函数代码都放到了hellogo中，这就是所谓的“静态链接”。是不是所有情况下，Go都不会依赖外部动态共享库呢？我们来看看下面这段代码：</p>
<pre><code>//server.go
package main

import (
    "log"
    "net/http"
    "os"
)

func main() {
    cwd, err := os.Getwd()
    if err != nil {
        log.Fatal(err)
    }

    srv := &amp;http.Server{
        Addr:    ":8000", // Normally ":443"
        Handler: http.FileServer(http.Dir(cwd)),
    }
    log.Fatal(srv.ListenAndServe())
}
</code></pre>
<p>我们利用Go标准库的net/http包写了一个fileserver，我们build一下该server，并查看它是否有外部依赖以及未定义的符号：</p>
<pre><code>$go build server.go
-rwxr-xr-x    1 tony  staff  5943828  6 27 14:47 server*

$otool -L server
server:
    /usr/lib/libSystem.B.dylib (compatibility version 0.0.0, current version 0.0.0)
    /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 0.0.0, current version 0.0.0)
    /System/Library/Frameworks/Security.framework/Versions/A/Security (compatibility version 0.0.0, current version 0.0.0)
    /usr/lib/libSystem.B.dylib (compatibility version 0.0.0, current version 0.0.0)
    /usr/lib/libSystem.B.dylib (compatibility version 0.0.0, current version 0.0.0)

$nm server |grep " U "
                 U _CFArrayGetCount
                 U _CFArrayGetValueAtIndex
                 U _CFDataAppendBytes
                 U _CFDataCreateMutable
                 U _CFDataGetBytePtr
                 U _CFDataGetLength
                 U _CFDictionaryGetValueIfPresent
                 U _CFEqual
                 U _CFNumberGetValue
                 U _CFRelease
                 U _CFStringCreateWithCString
                 U _SecCertificateCopyNormalizedIssuerContent
                 U _SecCertificateCopyNormalizedSubjectContent
                 U _SecKeychainItemExport
                 U _SecTrustCopyAnchorCertificates
                 U _SecTrustSettingsCopyCertificates
                 U _SecTrustSettingsCopyTrustSettings
                 U ___error
                 U ___stack_chk_fail
                 U ___stack_chk_guard
                 U ___stderrp
                 U _abort
                 U _fprintf
                 U _fputc
                 U _free
                 U _freeaddrinfo
                 U _fwrite
                 U _gai_strerror
                 U _getaddrinfo
                 U _getnameinfo
                 U _kCFAllocatorDefault
                 U _malloc
                 U _memcmp
                 U _nanosleep
                 U _pthread_attr_destroy
                 U _pthread_attr_getstacksize
                 U _pthread_attr_init
                 U _pthread_cond_broadcast
                 U _pthread_cond_wait
                 U _pthread_create
                 U _pthread_key_create
                 U _pthread_key_delete
                 U _pthread_mutex_lock
                 U _pthread_mutex_unlock
                 U _pthread_setspecific
                 U _pthread_sigmask
                 U _setenv
                 U _strerror
                 U _sysctlbyname
                 U _unsetenv

</code></pre>
<p>通过otool和nm的输出结果我们惊讶的看到：默认采用“静态链接”的Go程序怎么也要依赖外部的动态链接库，并且也包含了许多“未定义”的符号了呢？问题在于cgo。</p>
<h2>三、cgo对可移植性的影响</h2>
<p>默认情况下，Go的runtime环境变量CGO_ENABLED=1，即默认开始cgo，允许你在Go代码中调用C代码，Go的pre-compiled标准库的.a文件也是在这种情况下编译出来的。在$GOROOT/pkg/darwin_amd64中，我们遍历所有预编译好的标准库.a文件，并用nm输出每个.a的未定义符号，我们看到下面一些包是对外部有依赖的（动态链接）：</p>
<pre><code>=&gt; crypto/x509.a
                 U _CFArrayGetCount
                 U _CFArrayGetValueAtIndex
                 U _CFDataAppendBytes
                 ... ...
                 U _SecCertificateCopyNormalizedIssuerContent
                 U _SecCertificateCopyNormalizedSubjectContent
                 ... ...
                 U ___stack_chk_fail
                 U ___stack_chk_guard
                 U __cgo_topofstack
                 U _kCFAllocatorDefault
                 U _memcmp
                 U _sysctlbyname

=&gt; net.a
                 U ___error
                 U __cgo_topofstack
                 U _free
                 U _freeaddrinfo
                 U _gai_strerror
                 U _getaddrinfo
                 U _getnameinfo
                 U _malloc

=&gt; os/user.a
                 U __cgo_topofstack
                 U _free
                 U _getgrgid_r
                 U _getgrnam_r
                 U _getgrouplist
                 U _getpwnam_r
                 U _getpwuid_r
                 U _malloc
                 U _realloc
                 U _sysconf

=&gt; plugin.a
                 U __cgo_topofstack
                 U _dlerror
                 U _dlopen
                 U _dlsym
                 U _free
                 U _malloc
                 U _realpath$DARWIN_EXTSN

=&gt; runtime/cgo.a
                 ... ...
                 U _abort
                 U _fprintf
                 U _fputc
                 U _free
                 U _fwrite
                 U _malloc
                 U _nanosleep
                 U _pthread_attr_destroy
                 U _pthread_attr_getstacksize
                 ... ...
                 U _setenv
                 U _strerror
                 U _unsetenv

=&gt; runtime/race.a
                 U _OSSpinLockLock
                 U _OSSpinLockUnlock
                 U __NSGetArgv
                 U __NSGetEnviron
                 U __NSGetExecutablePath
                 U ___error
                 U ___fork
                 U ___mmap
                 U ___munmap
                 U ___stack_chk_fail
                 U ___stack_chk_guard
                 U __dyld_get_image_header
                .... ...
</code></pre>
<p>我们以os/user为例，在CGO_ENABLED=1，即cgo开启的情况下，os/user包中的lookupUserxxx系列函数采用了c版本的实现，我们看到在$GOROOT/src/os/user/lookup_unix.go中的build tag中包含了<strong>+build cgo</strong>。这样一来，在CGO_ENABLED=1，该文件将被编译，该文件中的c版本实现的lookupUser将被使用：</p>
<pre><code>// +build darwin dragonfly freebsd !android,linux netbsd openbsd solaris
// +build cgo

package user
... ...
func lookupUser(username string) (*User, error) {
    var pwd C.struct_passwd
    var result *C.struct_passwd
    nameC := C.CString(username)
    defer C.free(unsafe.Pointer(nameC))
    ... ...
}

</code></pre>
<p>这样来看，凡是依赖上述包的Go代码最终编译的可执行文件都是要有外部依赖的。不过我们依然可以通过disable CGO_ENABLED来编译出纯静态的Go程序：</p>
<pre><code>$CGO_ENABLED=0 go build -o server_cgo_disabled server.go

$otool -L server_cgo_disabled
server_cgo_disabled:
$nm server_cgo_disabled |grep " U "
</code></pre>
<p>如果你使用build的 “-x -v”选项，你将看到go compiler会重新编译依赖的包的静态版本，包括net、mime/multipart、crypto/tls等，并将编译后的.a(以包为单位)放入临时编译器工作目录($WORK)下，然后再静态连接这些版本。</p>
<h2>四、internal linking和external linking</h2>
<p>问题来了：在CGO_ENABLED=1这个默认值的情况下，是否可以实现纯静态连接呢？答案是可以。在$GOROOT/cmd/cgo/doc.go中，文档介绍了cmd/link的两种工作模式：internal linking和external linking。</p>
<h3>1、internal linking</h3>
<p>internal linking的大致意思是若用户代码中仅仅使用了net、os/user等几个标准库中的依赖cgo的包时，cmd/link默认使用internal linking，而无需启动外部external linker(如:gcc、clang等)，不过由于cmd/link功能有限，仅仅是将.o和pre-compiled的标准库的.a写到最终二进制文件中。因此如果标准库中是在CGO_ENABLED=1情况下编译的，那么编译出来的最终二进制文件依旧是动态链接的，即便在go build时传入-ldflags &#8216;extldflags “-static”&#8216;亦无用，因为根本没有使用external linker：</p>
<pre><code>$go build -o server-fake-static-link  -ldflags '-extldflags "-static"' server.go
$otool -L server-fake-static-link
server-fake-static-link:
    /usr/lib/libSystem.B.dylib (compatibility version 0.0.0, current version 0.0.0)
    /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 0.0.0, current version 0.0.0)
    /System/Library/Frameworks/Security.framework/Versions/A/Security (compatibility version 0.0.0, current version 0.0.0)
    /usr/lib/libSystem.B.dylib (compatibility version 0.0.0, current version 0.0.0)
    /usr/lib/libSystem.B.dylib (compatibility version 0.0.0, current version 0.0.0)
</code></pre>
<h3>2、external linking</h3>
<p>而external linking机制则是cmd/link将所有生成的.o都打到一个.o文件中，再将其交给外部的链接器，比如<a href="http://tonybai.com/tag/gcc">gcc</a>或clang去做最终链接处理。如果此时，我们在cmd/link的参数中传入-ldflags &#8216;extldflags “-static”&#8216;，那么gcc/clang将会去做静态链接，将.o中undefined的符号都替换为真正的代码。我们可以通过-linkmode=external来强制cmd/link采用external linker，还是以server.go的编译为例：</p>
<pre><code>$go build -o server-static-link  -ldflags '-linkmode "external" -extldflags "-static"' server.go
# command-line-arguments
/Users/tony/.bin/go18/pkg/tool/darwin_amd64/link: running clang failed: exit status 1
ld: library not found for -lcrt0.o
clang: error: linker command failed with exit code 1 (use -v to see invocation)
</code></pre>
<p>可以看到，cmd/link调用的clang尝试去静态连接libc的.a文件，但由于我的mac上仅仅有libc的dylib，而没有.a，因此静态连接失败。我找到一个ubuntu 16.04环境：重新执行上述构建命令：</p>
<pre><code># go build -o server-static-link  -ldflags '-linkmode "external" -extldflags "-static"' server.go
# ldd server-static-link
    not a dynamic executable
# nm server-static-link|grep " U "
</code></pre>
<p>该环境下libc.a和libpthread.a分别在下面两个位置：</p>
<pre><code>/usr/lib/x86_64-linux-gnu/libc.a
/usr/lib/x86_64-linux-gnu/libpthread.a
</code></pre>
<p>就这样，我们在CGO_ENABLED=1的情况下，也编译构建出了一个纯静态链接的Go程序。</p>
<p>如果你的代码中使用了C代码，并依赖cgo在go中调用这些c代码，那么cmd/link将会自动选择external linking的机制：</p>
<pre><code>//testcgo.go
package main

//#include &lt;stdio.h&gt;
// void foo(char *s) {
//    printf("%s\n", s);
// }
// void bar(void *p) {
//    int *q = (int*)p;
//    printf("%d\n", *q);
// }
import "C"
import (
    "fmt"
    "unsafe"
)

func main() {
    var s = "hello"
    C.foo(C.CString(s))

    var i int = 5
    C.bar(unsafe.Pointer(&amp;i))

    var i32 int32 = 7
    var p *uint32 = (*uint32)(unsafe.Pointer(&amp;i32))
    fmt.Println(*p)
}
</code></pre>
<p>编译testcgo.go：</p>
<pre><code># go build -o testcgo-static-link  -ldflags '-extldflags "-static"' testcgo.go
# ldd testcgo-static-link
    not a dynamic executable

vs.
# go build -o testcgo testcgo.go
# ldd ./testcgo
    linux-vdso.so.1 =&gt;  (0x00007ffe7fb8d000)
    libpthread.so.0 =&gt; /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fc361000000)
    libc.so.6 =&gt; /lib/x86_64-linux-gnu/libc.so.6 (0x00007fc360c36000)
    /lib64/ld-linux-x86-64.so.2 (0x000055bd26d4d000)

</code></pre>
<h2>五、小结</h2>
<p>本文探讨了Go的可移植性以及哪些因素对Go编译出的程序的移植性有影响：</p>
<ul>
<li>你的程序用了哪些标准库包？如果仅仅是非net、os/user等的普通包，那么你的程序默认将是纯静态的，不依赖任何c lib等外部动态链接库；</li>
<li>如果使用了net这样的包含cgo代码的标准库包，那么CGO_ENABLED的值将影响你的程序编译后的属性：是静态的还是动态链接的；</li>
<li>CGO_ENABLED=0的情况下，Go采用纯静态编译；</li>
<li>如果CGO_ENABLED=1，但依然要强制静态编译，需传递-linkmode=external给cmd/link。</li>
</ul>
<hr />
<p>微博：<a href="http://weibo.com/bigwhite20xx">@tonybai_cn</a><br />
微信公众号：iamtonybai<br />
github.com: https://github.com/bigwhite</p>
<p style='text-align:left'>&copy; 2017, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2017/06/27/an-intro-about-go-portability/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>为什么还用C编程？</title>
		<link>https://tonybai.com/2013/02/27/why-code-in-c-anymore/</link>
		<comments>https://tonybai.com/2013/02/27/why-code-in-c-anymore/#comments</comments>
		<pubDate>Wed, 27 Feb 2013 05:32:47 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[Cpp]]></category>
		<category><![CDATA[Opensource]]></category>
		<category><![CDATA[Performance]]></category>
		<category><![CDATA[Portability]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[Translate]]></category>
		<category><![CDATA[博客]]></category>
		<category><![CDATA[可移植]]></category>
		<category><![CDATA[性能]]></category>
		<category><![CDATA[程序员]]></category>
		<category><![CDATA[编程]]></category>
		<category><![CDATA[翻译]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=1204</guid>
		<description><![CDATA[本文翻译自Dr. Dobb&#39;s杂志主编Andrew Binstock的文章&#8220;Why Code in C Anymore?&#8221;，以下是翻译正文。 传统的那些选择C而不是C++的理由的说服力已经逐渐地被削弱。还有什么继续使用C的更好的理由么？ 一个 Dr. Dobb&#39;s的老读者最近问我：为何人们还在使用C编程。这个话题最近曾在我们站点的评论中出现过。早期也曾出现在与一些行业公司的对话过程中，尤其是微 软。在C++早期，根据你的需要，你可以有许多使用C或C++的理由；但随着C++的演化，C的大量传统的杰出特性已经变得不那么优越了。考虑到 这些点一般是在比较两门编程语言时首先会被考虑到的，因此我们来一起看一下。 性能。通常我们都认为C++应用的性能要比C的慢。但在大多主流平台上，这个性能上的差距在今天已经变得非常小了。比 如，Alioth上的计算机基准测试报告显示C++(运行在32位Linux上)在运行基准测试时的性能要比C慢27%。其他一些研究结果显示这个差 距或略大或略小些。但在几乎所有例子中，C++都是仅次于C的运行第二快的编程语言。它通常要比运行在JVM和.NET平台上的语言快出很多。因 此，尽管C在基准测试上依旧保持有优势，但对于大多已经可以接受Java性能的应用(比如企业应用或面向客户端的应用)而言，这个差距显得并不是那么重要 了。 普遍性。在嵌入式编程领域，C仍然保持着首选语言的舒适地位，这缘于这样一个事实：每个硬件供货商都会提供一个C编译器。而大家普遍 也认为C++在嵌入式开发方面表现没有那么强势。不过，当今大多提供编程工具的组件供货商也提供了C++编译器。(但PIC微控制器方面继续保持 着例外)。这是一个正在逐渐缩水的好处。 可移植性。C++曾被认为是一个可移植的老大难（实际上在C89标准出台以前，C也是一个老大难）。然而，当今的编译器对C++语 言的核心实现的十分充分，以至于大多软件可以通过重新编译进行移植，不需要什么调整。如果真的需要进行调整的话，请提供像Brian Kernighan曾经说过的那样的使用语言中段的代码（译注：所谓语言中段的代码，即那些平台无关，不涉及可移植性的代码语法和元素）。库的可移植 性是一个更令人头疼的因素，不过C库也存在同样的问题。在C和C++中，编译器的标准遵守程度迥异，因此使用那些没有获得全面支持的新特性 (C99、C11以及C++11）将会是一个内在的风险。也就是说，C89可能是世界上最具可移植性的代码了。(为此，当可移植性成为最关心的事 情的时候，我们将选择它。例如，Lua团队就因此选择了C，当然也同样考虑到了性能因素) 应该说从性能、普遍性以及可移植性方面来讲，C仍然对C++保持着优势，但这种优势正在逐步缩小。在这点上，C++社区做得非常出色，他们让用户 去处理那些实质性问题并采纳。问题是：这些缩水的优势对C++的好处是种补偿吗？这些好处包括面向对象，异常处理，更好的类型管理，模板，更丰富 的标准库等等。没有了这些好处，每个用C实现的工程可能感觉起来就像尝试用一柄剪刀去修剪草坪。 那些特性无疑有助于代码编写，但却要因此付出代价 &#8211; 复杂性，这也是C有别于C++的重要之处。C是为数不多的几种规模短小、足够简洁的通用编程语言，你可以轻松掌握其全部内容。事实上我们确实可能需要 完整地了解一门语言的全部细节，并且还要充分了解其标准库，达到无需查看API手册即可使用的程度。我不相信这在其他主流语言中是可行的，C++，当然不行。 短小是语言的吸引力之一。你可以快速学习它并快速成为有效率的开发者。简洁则因另一个较少被谈及的特性而被提高：极致的语言可读性。我这里指的是 语义上而不是语法上的。在语义方面，C中做某件事情的方法是有限的。因此，当你阅读代码时，无论是谁编写的代码，你会确切地知道代码的行为。相反，C++ 做同一件事情有很多种方法 &#8211; C++开发者喜欢的一种灵活性。正是由于C在这方面的清晰，它才成为一门用于编写复杂基础设施的卓越编程语言。也正是这个原因，JRockit JVM（现在Oracle的主要JVM）的原始作者选择了C。在几年前的一段对话中，他们阐述了他们选择C而不是C++的观点：他们可以更快速获得开发 者；当深入到代码中时，他们可以比使用C++更容易地理解这些代码。 仅凭这一点，C语言仍然是系统层代码的一个极佳选择：它快速，可移植，易于阅读和理解。对于那些更加强调开发效率的应用，无疑C++将继续在原生语言中占据主导位置，并且很可能扩大其足迹。 &#169; 2013, bigwhite. 版权所有.]]></description>
			<content:encoded><![CDATA[<p>本文翻译自<a href="http://www.drdobbs.com/">Dr. Dobb&#39;s杂志</a>主编Andrew Binstock的文章&ldquo;<a href="http://www.drdobbs.com/cpp/why-code-in-c-anymore/240149452">Why Code in C Anymore?</a>&rdquo;，以下是翻译正文。</p>
<p><b>传统的那些选择C而不是C++的理由的说服力已经逐渐地被削弱。还有什么继续使用C的更好的理由么？</b></p>
<p>一个 Dr. Dobb&#39;s的老读者最近问我：为何人们还在使用C编程。这个话题最近曾在我们站点的评论中出现过。早期也曾出现在与一些行业公司的对话过程中，尤其是微 软。在C++早期，根据你的需要，你可以有许多使用C或C++的理由；但随着C++的演化，C的大量传统的杰出特性已经变得不那么优越了。考虑到 这些点一般是在比较两门编程语言时首先会被考虑到的，因此我们来一起看一下。</p>
<p><b>性能</b>。通常我们都认为C++应用的性能要比C的慢。但在大多主流平台上，这个性能上的差距在今天已经变得非常小了。比 如，Alioth上的计算机基准测试报告显示C++(运行在32位Linux上)在运行基准测试时的性能要比C慢27%。其他一些研究结果显示这个差 距或略大或略小些。但在几乎所有例子中，C++都是仅次于C的运行第二快的编程语言。它通常要比运行在JVM和.NET平台上的语言快出很多。因 此，尽管C在基准测试上依旧保持有优势，但对于大多已经可以接受Java性能的应用(比如企业应用或面向客户端的应用)而言，这个差距显得并不是那么重要 了。</p>
<p><b>普遍性</b>。在嵌入式编程领域，C仍然保持着首选语言的舒适地位，这缘于这样一个事实：每个硬件供货商都会提供一个C编译器。而大家普遍 也认为C++在嵌入式开发方面表现没有那么强势。不过，当今大多提供编程工具的组件供货商也提供了C++编译器。(但PIC微控制器方面继续保持 着例外)。这是一个正在逐渐缩水的好处。</p>
<p><b>可移植性</b>。C++曾被认为是一个可移植的老大难（实际上在C89标准出台以前，C也是一个老大难）。然而，当今的编译器对C++语 言的核心实现的十分充分，以至于大多软件可以通过重新编译进行移植，不需要什么调整。如果真的需要进行调整的话，请提供像Brian Kernighan曾经说过的那样的使用语言中段的代码（译注：所谓语言中段的代码，即那些平台无关，不涉及可移植性的代码语法和元素）。库的可移植 性是一个更令人头疼的因素，不过C库也存在同样的问题。在C和C++中，编译器的标准遵守程度迥异，因此使用那些没有获得全面支持的新特性 (C99、C11以及C++11）将会是一个内在的风险。也就是说，C89可能是世界上最具可移植性的代码了。(为此，当可移植性成为最关心的事 情的时候，我们将选择它。例如，Lua团队就因此选择了C，当然也同样考虑到了性能因素)</p>
<p>应该说从性能、普遍性以及可移植性方面来讲，C仍然对C++保持着优势，但这种优势正在逐步缩小。在这点上，C++社区做得非常出色，他们让用户 去处理那些实质性问题并采纳。问题是：这些缩水的优势对C++的好处是种补偿吗？这些好处包括面向对象，异常处理，更好的类型管理，模板，更丰富 的标准库等等。没有了这些好处，每个用C实现的工程可能感觉起来就像尝试用一柄剪刀去修剪草坪。</p>
<p>那些特性无疑有助于代码编写，但却要因此付出代价 &#8211; 复杂性，这也是C有别于C++的重要之处。C是为数不多的几种规模短小、足够简洁的通用编程语言，你可以轻松掌握其全部内容。事实上我们确实可能需要 完整地了解一门语言的全部细节，并且还要充分了解其标准库，达到无需查看API手册即可使用的程度。我不相信这在其他主流语言中是可行的，C++，当然不行。</p>
<p>短小是语言的吸引力之一。你可以快速学习它并快速成为有效率的开发者。简洁则因另一个较少被谈及的特性而被提高：极致的语言可读性。我这里指的是 语义上而不是语法上的。在语义方面，C中做某件事情的方法是有限的。因此，当你阅读代码时，无论是谁编写的代码，你会确切地知道代码的行为。相反，C++ 做同一件事情有很多种方法 &#8211; C++开发者喜欢的一种灵活性。正是由于C在这方面的清晰，它才成为一门用于编写复杂基础设施的卓越编程语言。也正是这个原因，JRockit JVM（现在Oracle的主要JVM）的原始作者选择了C。在几年前的一段对话中，他们阐述了他们选择C而不是C++的观点：他们可以更快速获得开发 者；当深入到代码中时，他们可以比使用C++更容易地理解这些代码。</p>
<p>仅凭这一点，C语言仍然是系统层代码的一个极佳选择：它快速，可移植，易于阅读和理解。对于那些更加强调开发效率的应用，无疑C++将继续在原生语言中占据主导位置，并且很可能扩大其足迹。</p>
<p style='text-align:left'>&copy; 2013, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2013/02/27/why-code-in-c-anymore/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
	</channel>
</rss>
