<?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; Darwin</title>
	<atom:link href="http://tonybai.com/tag/darwin/feed/" rel="self" type="application/rss+xml" />
	<link>https://tonybai.com</link>
	<description>一个程序员的心路历程</description>
	<lastBuildDate>Tue, 23 Jun 2026 22:26:13 +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语言中的rune：一段跨越30年的Plan 9往事与UTF-8的诞生传奇</title>
		<link>https://tonybai.com/2025/05/16/how-rune-came/</link>
		<comments>https://tonybai.com/2025/05/16/how-rune-came/#comments</comments>
		<pubDate>Thu, 15 May 2025 23:41:28 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[ANSIC]]></category>
		<category><![CDATA[bluesky]]></category>
		<category><![CDATA[BSD]]></category>
		<category><![CDATA[C99]]></category>
		<category><![CDATA[codepoint]]></category>
		<category><![CDATA[Darwin]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[IBM]]></category>
		<category><![CDATA[int32]]></category>
		<category><![CDATA[ISO10646]]></category>
		<category><![CDATA[KenThompson]]></category>
		<category><![CDATA[plan9]]></category>
		<category><![CDATA[RobPike]]></category>
		<category><![CDATA[rune]]></category>
		<category><![CDATA[RussCox]]></category>
		<category><![CDATA[String]]></category>
		<category><![CDATA[Unicode]]></category>
		<category><![CDATA[别名]]></category>
		<category><![CDATA[码点]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=4713</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2025/05/16/how-rune-came 大家好，我是Tony Bai。 作为 Gopher，我们每天都在和 rune 打交道。在 Go 语言中，它通常被解释为“一个 Unicode 码点”，官方文档也说引入这个术语是为了“简洁”。但你是否曾好奇，这个略带神秘色彩的词汇，究竟源自何方？仅仅是为了简洁吗？ 最近，Connor Taffe的一篇精彩博文以及 Go语言之父 Rob Pike 的亲自确认，为我们揭开了一段跨越三十余年，从 Plan 9 操作系统到 UTF-8 编码诞生，再到 Go 语言的历史传奇。今天，就让我们一起，深入 rune 背后的故事。 一句“简洁”，一段 Plan 9 往事 Connor文章中引用的Adam Pritchard的关于限制字符串长度的文章中提到：“请注意，在 Go 中，Unicode 码点通常被称为‘rune’。（Go 似乎是为了简洁而引入了这个术语。）” 而 Go 官方博客《Strings, bytes, runes, and characters in Go》也说：“‘Code point’有点拗口，所以 Go 引入了一个更短的术语：rune。” 然而，真相远不止于此。Rob Pike 最近在 Bluesky 上澄清(如上图)，rune [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2025/how-rune-came-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2025/05/16/how-rune-came">本文永久链接</a> &#8211; https://tonybai.com/2025/05/16/how-rune-came</p>
<p>大家好，我是Tony Bai。</p>
<p>作为 Gopher，我们每天都在和 rune 打交道。在 Go 语言中，它通常被解释为“一个 Unicode 码点”，官方文档也说引入这个术语是为了“简洁”。但你是否曾好奇，这个略带神秘色彩的词汇，究竟源自何方？仅仅是为了简洁吗？</p>
<p>最近，<a href="https://connor.zip/posts/2025-05-03-rune">Connor Taffe的一篇精彩博文</a>以及 <a href="https://bsky.app/profile/robpike.io/post/3lokt3qzvos2h">Go语言之父 Rob Pike 的亲自确认</a>，为我们揭开了一段跨越三十余年，从 Plan 9 操作系统到 UTF-8 编码诞生，再到 Go 语言的历史传奇。今天，就让我们一起，深入 rune 背后的故事。</p>
<h2>一句“简洁”，一段 Plan 9 往事</h2>
<p>Connor文章中引用的Adam Pritchard的关于<a href="https://adam-p.ca/blog/2025/04/string-length/">限制字符串长度</a>的文章中提到：“请注意，在 Go 中，Unicode 码点通常被称为‘rune’。（Go 似乎是为了简洁而引入了这个术语。）” 而 Go 官方博客《<a href="https://go.dev/blog/strings">Strings, bytes, runes, and characters in Go</a>》也说：“‘Code point’有点拗口，所以 Go 引入了一个更短的术语：rune。”</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/how-rune-came-2.png" alt="Rob Pike 在 Bluesky 上的发言截图" /></p>
<p>然而，真相远不止于此。Rob Pike 最近在 Bluesky 上澄清(如上图)，rune 这个词实际上是 <strong>Ken Thompson</strong> 在一次为 Plan 9 寻找一个不同于 char（用于字节）的类型名称的头脑风暴中“得意地”提出的，Rob Pike 当即表示赞同。更关键的是，Rob Pike 随后确认，<strong>这个命名发生在 Plan 9 为 UTF 和 ISO 10646 寻找类型名称的时期，具体是1991 年 12 月 8 日的晚上</strong>！远早于 Unicode 和 UTF-8 的广泛应用，也比 Go 语言的诞生早了数十年。</p>
<p>是的，你没看错，rune 的故事，始于 Plan 9，那个由贝尔实验室传奇人物们（包括 Rob Pike, Ken Thompson 等）创造的操作系统。Go 语言深受 Plan 9 的影响，从链接器架构、并发原语 channel、标识符大小写的可见性规则，到对简洁性的极致追求，都带着浓厚的 Plan 9 印记。rune 便是这血脉传承中的一环。</p>
<h2>餐巾纸上的革命：UTF-8 的诞生传奇</h2>
<p>要理解 rune 在 Plan 9 中的意义，就不得不提 UTF-8 的诞生。Connor 的文章中引用了一封 Rob Pike 在 2003 年的邮件，详细披露了这段鲜为人知的历史，纠正了“IBM 设计 UTF-8，Plan 9 实现它”的说法。</p>
<p>故事发生在 1992 年 9 月左右的一个晚上，新泽西一家小餐馆的餐巾纸上：</p>
<ul>
<li><strong>缘起：</strong> Plan 9 当时使用 ISO 10646 最初的 UTF（一种16位字符编码）来支持宽字符，但团队对它非常不满。Rob Pike 形容道：“UTF 太糟糕了。它有模192的算术，而且在没有除法硬件的老 SPARC 机器上几乎不可能高效实现。像【/*】这样的字符串可能出现在西里尔字符中间，导致你的俄文文本变成一个 C 语言注释。还有更多问题。它作为一种编码根本不实用。”</li>
<li><strong>契机：</strong> 一天下午，X/Open 委员会的一些人（据 Rob Pike 回忆可能来自 IBM 奥斯汀）打来电话，希望 Ken 和 Rob 审查他们的 FSS-UTF (File System Safe UTF) 设计。Ken 和 Rob 意识到这是一个用他们的经验设计一个真正优秀的标准，并让 X/Open 将其推广出去的机会。</li>
<li><strong>餐巾纸上的灵感：</strong> 他们接受了挑战，条件是必须快速完成。于是，在那个决定性的晚餐上，<strong>Ken Thompson 在餐巾纸上构想出了 UTF-8 的位打包方案。</strong></li>
<li><strong>闪电般的实现：</strong> 晚餐后回到实验室，他们便向 X/Open 解释了新方案，并承诺在周一前（据信是 X/Open 的重要投票日）拿出一个完整的运行系统。当晚，Ken 写了打包和解包代码，Rob Pike 则开始修改 C 库和图形库。到周五的某个时候，Plan 9 已经完全运行在后来被称为 UTF-8 的编码上了。</li>
</ul>
<p>Rob Pike 在邮件中强调，他们之所以要“另起炉灶”，是因为 FSS-UTF 缺少他们认为至关重要的特性之一：<strong>支持定位到文件或流的中间，并读取有效字符，或处理损坏的字符。</strong> Ken Thompson 设计的 UTF-8 完美地解决了这个问题。</p>
<p>对比 Ken Thompson 当时提出的 UTF-8 方案(如下图)和 FSS-UTF，我们可以看到 UTF-8 的精妙之处：后续字节以 10 开头，与首字节的 110、1110 等模式区分开来，确保了自同步性和对 ASCII 的兼容性。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/how-rune-came-3.png" alt="" /></p>
<h2>Rune 的首次亮相与演变</h2>
<p>那么，Rune 这个词是什么时候正式与这种新的字符表示方式联系起来的呢？Rob Pike 在其关于 Plan 9 UTF-8 实现的论文《Hello World》中写道：</p>
<blockquote>
<p>“在语义层面上，ANSI C 允许（但并未限制）宽字符的概念，并且允许此类字符串和字符常量。我们选择 unsigned short 作为宽字符类型。在库中，Rune 一词由 typedef 定义为等同于 unsigned short，并用于表示 一个Unicode 字符。”</p>
</blockquote>
<p>这似乎是 Rune 作为一种特定类型名称，用于指代 Unicode 字符（码点）的最早文献记录。最初在 Plan 9 C 中，Rune 是一个 16 位无符号短整型，足以表示当时的 Unicode 基本多文种平面（BMP）。</p>
<p>而到了 Go 语言，rune 被定义为 int32 的别名。这是因为自 1992 年以来，Unicode 已经扩展，需要更大的空间来表示所有码点（UCS-4 定义了 31 位码空间）。Go 语言标准库中的 unicode/utf8 包也定义了 UTFMax = 4，表明一个 rune 最多可以用 4 个字节的 UTF-8 编码表示。有趣的是，在 Russ Cox 移植的 plan9 port 中，Rune 类型在 2009 年末也被修改为了 unsigned int，同样是为了支持更广的码点范围。</p>
<p>Ken Thompson 在最初的邮件中提到：“4、5 和 6 字节序列只是出于政治原因才存在的。我更愿意删除它们。” 这也印证了早期设计者对编码效率和实用性的极致追求。</p>
<h2>Rune 的足迹：从 Plan 9 到更广阔的世界</h2>
<p>Rune 这个术语，并没有止步于 Plan 9。通过 Paul Borman 的贡献，Plan 9 的 rune 功能被整合进了 4.4 BSD。从此，rune 开始在更广阔的 Unix 世界留下足迹：</p>
<ul>
<li><strong>FreeBSD</strong> 继承了 4.4 BSD 的 rune 函数，尽管后来推荐使用 ISO C99 的宽字符工具。</li>
<li><strong>Apple 的 Darwin 内核</strong>，作为 BSD 的衍生，也包含了 rune_t 类型。</li>
<li>C 标准库实现如 <strong>newlib</strong> 也包含了源自 BSD 4.4 的 rune 功能。</li>
<li><strong>Android</strong> 通过 plan9port 移植了 Plan 9 的 libutf，其中自然也包含了 rune。</li>
<li>甚至，<strong>微软的 .NET</strong> 在引入 System.Text.Rune 类型时，其灵感也明确来自 Go 语言，<a href="https://github.com/dotnet/runtime/issues/23578">这在其 GitHub issue 中由 Miguel de Icaza 提及</a>。</li>
</ul>
<p>可见，rune 这个由 Ken Thompson 灵光一闪提出的词汇，承载着一段从贝尔实验室 Plan 9 开始，经由 BSD 社区，最终深刻影响了包括 Go 在内的现代编程语言和操作系统的字符处理历史。</p>
<h2>小结：rune 不只是简洁</h2>
<p>通过Rob Pike的亲自确认，我们应该知道，当我们今天再看到 Go 语言中的 rune 时，它不仅仅是为了“简洁”而对“Unicode code point”的替换。它是一个承载着厚重历史的符号，是 Go 语言设计者们深厚技术底蕴和创新精神的体现，是 Plan 9 简洁哲学与 UTF-8 实用主义的结晶。</p>
<p>理解 rune 的来龙去脉，有助于我们更深刻地体会 Go 语言在文本处理、字符串操作以及 Unicode 支持方面的设计考量，也让我们对这门语言背后的巨匠们多一份敬意。下一次，当你在 Go 代码中写下 rune 时，或许会想起那个在新泽西餐馆餐巾纸上诞生的传奇，以及那段跨越三十余年的 Plan 9 往事。</p>
<h2>参考文献</h2>
<ul>
<li><a href="https://connor.zip/posts/2025-05-03-rune">Rune by Connor Taffe</a> &#8211; https://connor.zip/posts/2025-05-03-rune</li>
<li><a href="https://bsky.app/profile/robpike.io/post/3lokt3qzvos2h">Rob Pike on Bluesky, re: origin of “rune”</a> &#8211; https://bsky.app/profile/robpike.io/post/3lokt3qzvos2h</li>
<li><a href="https://www.cl.cam.ac.uk/~mgk25/ucs/utf-8-history.txt">Rob Pike, Email “UTF-8 history”</a> &#8211; https://www.cl.cam.ac.uk/~mgk25/ucs/utf-8-history.txt</li>
<li><a href="https://connor.zip/resources/pdfs/utf8.pdf">Rob Pike, “Hello World” (Plan 9 UTF-8 implementation paper)</a> &#8211; https://connor.zip/resources/pdfs/utf8.pdf</li>
</ul>
<hr />
<p><strong>聊一聊：</strong></p>
<ul>
<li><strong>在了解了 rune 的历史后，你对 Go 语言的设计是否有新的认识？</strong></li>
<li><strong>UTF-8 诞生的故事中，有哪些细节让你印象深刻？</strong></li>
<li><strong>你认为这种对历史渊源的挖掘，对我们理解和使用一门编程语言有何帮助？</strong></li>
</ul>
<p>欢迎在评论区分享你的看法！如果你觉得这篇文章有趣且有价值，也请<strong>转发给你身边的 Gopher 朋友们</strong>，让更多人了解 rune 背后的故事。</p>
<hr />
<p>今天我们一起挖掘了 rune 这个小小术语背后波澜壮阔的历史，感受到了 Go 语言与 Plan 9、UTF-8 的深厚渊源。<strong>真正理解一门语言，往往需要我们深入其“根源”，探究其设计选择背后的“为什么”。</strong></p>
<p>这里，我邀请你加入我在<strong>极客时间</strong>的专栏 <strong>“TonyBai · Go 语言进阶课”</strong>。</p>
<p>在这门课程中，我们将一起：</p>
<ul>
<li><strong>夯实基础，突破语法认知瓶颈：</strong> 深入剖析那些看似熟悉却暗藏玄机的核心概念。</li>
<li><strong>设计先行，奠定高质量代码基础：</strong> 学习如何进行合理的程序骨架、并发设计、包设计、接口设计以及API设计。</li>
<li><strong>工程实践，锻造生产级 Go 服务：</strong> 掌握构建可观测性、性能调优、故障排查等硬核技能。</li>
</ul>
<p><strong>理解“过去”是为了更好地走向“未来”。</strong> 就像我们今天了解 rune 的故事一样，在《Go语言进阶课》中，我们将一起探索更多 Go 语言的设计精髓与实践智慧，助你完成从“熟练”到“精通”的蜕变。</p>
<p><strong>扫描下方二维码或点击[阅读原文]，立即加入，开启你的 Go 语言精进之旅！</strong></p>
<p><img src="https://tonybai.com/wp-content/uploads/course-card/go-advanced-course-4.png" alt="" /></p>
<p>期待与你在极客时间相遇，共同探索 Go 语言的深层魅力！</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; 2025, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2025/05/16/how-rune-came/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Go包构建：专家也未必了解的文件选择细节</title>
		<link>https://tonybai.com/2024/11/21/go-source-file-selection-details-when-building-package/</link>
		<comments>https://tonybai.com/2024/11/21/go-source-file-selection-details-when-building-package/#comments</comments>
		<pubDate>Wed, 20 Nov 2024 22:57:08 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[aix]]></category>
		<category><![CDATA[Android]]></category>
		<category><![CDATA[BSD]]></category>
		<category><![CDATA[buildconstraints]]></category>
		<category><![CDATA[buildtag]]></category>
		<category><![CDATA[Cgo]]></category>
		<category><![CDATA[Darwin]]></category>
		<category><![CDATA[fmt]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[go1.23]]></category>
		<category><![CDATA[GOARCH]]></category>
		<category><![CDATA[gobuild]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[golist]]></category>
		<category><![CDATA[gomodule]]></category>
		<category><![CDATA[GOOS]]></category>
		<category><![CDATA[Go语言精进之路]]></category>
		<category><![CDATA[illumos]]></category>
		<category><![CDATA[iOS]]></category>
		<category><![CDATA[JS]]></category>
		<category><![CDATA[Package]]></category>
		<category><![CDATA[ReleaseTags]]></category>
		<category><![CDATA[toolchain]]></category>
		<category><![CDATA[Unix]]></category>
		<category><![CDATA[wasm]]></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=4405</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2024/11/21/go-source-file-selection-details-when-building-package 在Go语言开发中，包（package）是代码组织的基本单位，也是基本的构建单元。Go编译器会将每个包构建成一个目标文件(.a)，然后通过链接器将这些目标文件链接在一起，形成最终的可执行程序。 尽管Go包的构建过程看似简单，但实际上蕴含着许多值得深入了解的细节。例如，当我们执行go build命令时，Go编译器是如何选择需要编译的源文件的？你可能会回答：“不就是通过文件名中的ARCH和OS标识以及构建约束（build constraints）来选择的吗？” 虽然你的答案并没有错，但如果我进一步提出以下问题，你是否还能给出确切的答案呢？ 假设一个Go源文件使用了如下的构建约束： //go:build unix package foo // ... ... 在执行GOOS=android go build时，这个文件是否会被编译？如果执行的是GOOS=aix go build呢？而“unix”究竟包含了哪些操作系统？ 再进一步，当一个源文件的文件名中包含ARCH和操作系统标识，并且文件内容中也使用了构建约束时，Go编译器会如何处理这些信息的优先级？ 即使是经验丰富的Go专家，对于上述在包构建过程中涉及的文件选择细节，可能也只能给出模糊的答案。 在实际开发中，我们常常需要针对不同操作系统和架构编写特定的代码，这意味着灵活性与复杂性并存。Go的构建约束和文件名约定虽然为我们提供了灵活性，但也带来了额外的复杂性。理解这些规则不仅有助于优化构建过程，还能有效避免潜在的错误和不必要的麻烦。 在这篇文章中，我将与大家探讨Go包构建过程中源文件选择的细节，包括文件名中ARCH和os标识约定和构建约束的作用，以及二者的优先级处理问题。希望通过这些内容，帮助开发者更好地掌握Go语言的构建机制，从而提高开发效率。 为了更好地说明Go包构建时的文件选择逻辑，我们先从Go包构建的一些“表象”说起。 注：在本文中，我们将使用Go 1.17引入的新版build constraints写法：//go:build ，之前的// +build aix darwin dragonfly freebsd js,wasm &#8230;写法已经不再被推荐使用。如果你想对旧版build constraints写法有一个全面了解以便与新写法对比，推荐阅读我的《Go语言精进之路：从新手到高手的编程思想、方法和技巧》第2册。 1. 表象 在Go工程中，通常一个目录对应一个Go包，每个Go包下可以存在多个以.go为后缀的Go源文件，这些源文件只能具有唯一的包名（测试源文件除外），以标准库fmt包为例，它的目录下的源文件列表如下(以Go 1.23.0源码为例)： $ls $GOROOT/src/fmt doc.go export_test.go print.go stringer_example_test.go errors.go fmt_test.go scan.go stringer_test.go errors_test.go format.go scan_test.go [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/go-source-file-selection-details-when-building-package-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2024/11/21/go-source-file-selection-details-when-building-package">本文永久链接</a> &#8211; https://tonybai.com/2024/11/21/go-source-file-selection-details-when-building-package</p>
<p>在Go语言开发中，<a href="https://tonybai.com/2023/06/18/go-package-design-guide/">包（package）是代码组织的基本单位</a>，也是基本的构建单元。Go编译器会将每个包构建成一个目标文件(.a)，然后通过链接器将这些目标文件链接在一起，形成最终的可执行程序。</p>
<p>尽管Go包的构建过程看似简单，但实际上蕴含着许多值得深入了解的细节。例如，当我们执行go build命令时，Go编译器是如何选择需要编译的源文件的？你可能会回答：“不就是通过文件名中的ARCH和OS标识以及构建约束（build constraints）来选择的吗？” 虽然你的答案并没有错，但如果我进一步提出以下问题，你是否还能给出确切的答案呢？</p>
<p>假设一个Go源文件使用了如下的构建约束：</p>
<pre><code>//go:build unix

package foo
// ... ...
</code></pre>
<p>在执行GOOS=android go build时，这个文件是否会被编译？如果执行的是GOOS=aix go build呢？而“unix”究竟包含了哪些操作系统？</p>
<p>再进一步，当一个源文件的文件名中包含ARCH和操作系统标识，并且文件内容中也使用了构建约束时，Go编译器会如何处理这些信息的优先级？</p>
<p>即使是经验丰富的Go专家，对于上述在包构建过程中涉及的文件选择细节，可能也只能给出模糊的答案。</p>
<p>在实际开发中，我们常常需要针对不同操作系统和架构编写特定的代码，这意味着灵活性与复杂性并存。Go的构建约束和文件名约定虽然为我们提供了灵活性，但也带来了额外的复杂性。理解这些规则不仅有助于优化构建过程，还能有效避免潜在的错误和不必要的麻烦。</p>
<p>在这篇文章中，我将与大家探讨<strong>Go包构建过程中源文件选择的细节</strong>，包括文件名中ARCH和os标识约定和构建约束的作用，以及二者的优先级处理问题。希望通过这些内容，帮助开发者更好地掌握Go语言的构建机制，从而提高开发效率。</p>
<p>为了更好地说明Go包构建时的文件选择逻辑，我们先从Go包构建的一些“表象”说起。</p>
<blockquote>
<p>注：在本文中，我们将使用<a href="https://tonybai.com/2021/08/17/some-changes-in-go-1-17">Go 1.17</a>引入的新版build constraints写法：//go:build ，之前的// +build aix darwin dragonfly freebsd js,wasm &#8230;写法已经不再被推荐使用。如果你想对旧版build constraints写法有一个全面了解以便与新写法对比，推荐阅读我的<a href="https://book.douban.com/subject/35720729/">《Go语言精进之路：从新手到高手的编程思想、方法和技巧》第2册</a>。</p>
</blockquote>
<h2>1. 表象</h2>
<p>在Go工程中，通常一个目录对应一个Go包，每个Go包下可以存在多个以.go为后缀的Go源文件，这些源文件只能具有唯一的包名（测试源文件除外），以标准库fmt包为例，它的目录下的源文件列表如下(以<a href="https://tonybai.com/2024/08/19/some-changes-in-go-1-23/">Go 1.23.0</a>源码为例)：</p>
<pre><code>$ls $GOROOT/src/fmt
doc.go              export_test.go          print.go            stringer_example_test.go
errors.go           fmt_test.go         scan.go             stringer_test.go
errors_test.go          format.go           scan_test.go
example_test.go         gostringer_example_test.go  state_test.go
</code></pre>
<p>在这些文件中，哪些最终进入到了fmt包的目标文件(fmt.a)中呢？<strong>贴心的Go工具链</strong>为我们提供了查看方法：</p>
<pre><code>$go list -f '{{.GoFiles}}' fmt
[doc.go errors.go format.go print.go scan.go]
</code></pre>
<p>对于独立于目标ARCH和OS的fmt包来说，其Go源文件的选择似乎要简单一些。我们看到，除了包测试文件(xxx&#95;test.go)，其他文件都被编译到了最终的fmt包中。</p>
<p>我们再来看一个与目标ARCH和OS相关性较高的net包。除去子目录，这个包目录下的Go源文件数量大约有220多个，但在<strong>macOS/amd64</strong>下通过go list查看最终进入net包目标文件的文件，大约只有几十个：</p>
<pre><code>$go list -f '{{.GoFiles}}' net
[addrselect.go cgo_darwin.go cgo_unix.go cgo_unix_syscall.go conf.go dial.go dnsclient.go dnsclient_unix.go dnsconfig.go dnsconfig_unix.go error_posix.go error_unix.go fd_posix.go fd_unix.go file.go file_unix.go hook.go hook_unix.go hosts.go interface.go interface_bsd.go interface_darwin.go ip.go iprawsock.go iprawsock_posix.go ipsock.go ipsock_posix.go lookup.go lookup_unix.go mac.go mptcpsock_stub.go net.go netcgo_off.go netgo_off.go nss.go parse.go pipe.go port.go port_unix.go rawconn.go rlimit_unix.go sendfile_unix_alt.go sock_bsd.go sock_posix.go sockaddr_posix.go sockopt_bsd.go sockopt_posix.go sockoptip_bsdvar.go sockoptip_posix.go splice_stub.go sys_cloexec.go tcpsock.go tcpsock_posix.go tcpsock_unix.go tcpsockopt_darwin.go tcpsockopt_posix.go udpsock.go udpsock_posix.go unixsock.go unixsock_posix.go unixsock_readmsg_cloexec.go writev_unix.go]
</code></pre>
<p>接下来，我们跳出Go标准库，来看一个自定义的示例：</p>
<pre><code>$tree -F buildconstraints/demo1
buildconstraints/demo1
├── foo/
│   ├── f1_android.go
│   ├── f2_linux.go
│   └── f3_darwin.go
└── go.mod

// buildconstraints/demo1/foo/f1_android.go 

//go:build linux

package foo

func F1() {
}

// buildconstraints/demo1/foo/f2_linux.go
//go:build android

package foo

func F2() {
}

// buildconstraints/demo1/foo/f3_darwin.go
//go:build android

package foo

func F3() {
}
</code></pre>
<p>在GOOS=android下构建buildconstraints/demo1/foo这个包，哪些文件会被选出来呢，看下面输出结果：</p>
<pre><code>$GOOS=android go list -f '{{.GoFiles}}' github.com/bigwhite/demo1/foo
[f1_android.go f2_linux.go]
</code></pre>
<p>如果说前两个示例还好理解，那这第三个示例很可能会让很多开发者觉得有些“发蒙”。 别急，上面三个示例都是表象，接下来，我们就来仔细探索一下Go构建时的文件选择机制。</p>
<h2>2. 文件选择机制</h2>
<p>Go包构建时选择源文件的机制还是蛮繁琐的，我们需要从源码入手梳理出其主要逻辑，在Go 1.23版本中，Go包构建过程源文件选择逻辑的代码位于\$GOROOT/src/go/build/build.go中，这个源文件有2k多行，不过不用担心，我这里会替你把主要调用逻辑梳理为下图：</p>
<p><img src="https://tonybai.com/wp-content/uploads/go-source-file-selection-details-when-building-package-2.png" alt="" /></p>
<p>函数Import调用Default.Import去获取包的详细信息，信息用build.Package结构表示：</p>
<pre><code>// $GOROOT/src/go/build/build.go
// A Package describes the Go package found in a directory.
  type Package struct {
      Dir           string   // directory containing package sources
      Name          string   // package name
      ImportComment string   // path in import comment on package statement
      Doc           string   // documentation synopsis
      ImportPath    string   // import path of package ("" if unknown)
      Root          string   // root of Go tree where this package lives
      SrcRoot       string   // package source root directory ("" if unknown)
      PkgRoot       string   // package install root directory ("" if unknown)
      PkgTargetRoot string   // architecture dependent install root directory ("" if unknown)
      BinDir        string   // command install directory ("" if unknown)
      Goroot        bool     // package found in Go root
      PkgObj        string   // installed .a file
      AllTags       []string // tags that can influence file selection in this directory
      ConflictDir   string   // this directory shadows Dir in $GOPATH
      BinaryOnly    bool     // cannot be rebuilt from source (has //go:binary-only-package comment)

      // Source files
      GoFiles           []string // .go source files (excluding CgoFiles, TestGoFiles, XTestGoFiles)
      ... ...
</code></pre>
<p>其中的GoFiles就是参与Go包编译的源文件列表。</p>
<p>Default是默认的上下文信息，包括构建所需的默认goenv中几个环境变量，比如GOARCH、GOOS等的值：</p>
<pre><code>// Default is the default Context for builds.
// It uses the GOARCH, GOOS, GOROOT, and GOPATH environment variables
// if set, or else the compiled code's GOARCH, GOOS, and GOROOT.
var Default Context = defaultContext()
</code></pre>
<p>Context的Import方法代码行数很多，对于要了解文件选择细节的我们来说，其中最重要的调用是Context的matchFile方法。</p>
<p>matchFile正是那个<strong>用于确定某个Go源文件是否应该被选入最终包文件中的方法</strong>。它内部的逻辑可以分为两个主要步骤。</p>
<p>第一步是<strong>调用Context的goodOSArchFile方法对Go源文件的名字进行判定</strong>，goodOSArchFile方法的判定也有两个子步骤：</p>
<ul>
<li>判断名字中的OS和ARCH是否在Go支持的OS和ARCH列表中</li>
</ul>
<p>当前Go支持的OS和ARCH在syslist.go文件中有定义：</p>
<pre><code>// $GOROOT/src/go/build/syslist.go

// knownArch is the list of past, present, and future known GOARCH values.
// Do not remove from this list, as it is used for filename matching.
var knownArch = map[string]bool{
    "386":         true,
    "amd64":       true,
    "amd64p32":    true,
    "arm":         true,
    "armbe":       true,
    "arm64":       true,
    "arm64be":     true,
    "loong64":     true,
    "mips":        true,
    "mipsle":      true,
    "mips64":      true,
    "mips64le":    true,
    "mips64p32":   true,
    "mips64p32le": true,
    "ppc":         true,
    "ppc64":       true,
    "ppc64le":     true,
    "riscv":       true,
    "riscv64":     true,
    "s390":        true,
    "s390x":       true,
    "sparc":       true,
    "sparc64":     true,
    "wasm":        true,
}

// knownOS is the list of past, present, and future known GOOS values.
// Do not remove from this list, as it is used for filename matching.
// If you add an entry to this list, look at unixOS, below.
var knownOS = map[string]bool{
    "aix":       true,
    "android":   true,
    "darwin":    true,
    "dragonfly": true,
    "freebsd":   true,
    "hurd":      true,
    "illumos":   true,
    "ios":       true,
    "js":        true,
    "linux":     true,
    "nacl":      true,
    "netbsd":    true,
    "openbsd":   true,
    "plan9":     true,
    "solaris":   true,
    "wasip1":    true,
    "windows":   true,
    "zos":       true,
}
</code></pre>
<p>我们也可以通过下面命令查看：</p>
<pre><code>$go tool dist list
aix/ppc64
android/386
android/amd64
android/arm
android/arm64
darwin/amd64
darwin/arm64
dragonfly/amd64
freebsd/386
freebsd/amd64
freebsd/arm
freebsd/arm64
freebsd/riscv64
illumos/amd64
ios/amd64
ios/arm64
js/wasm
linux/386
linux/amd64
linux/arm
linux/arm64
linux/loong64
linux/mips
linux/mips64
linux/mips64le
linux/mipsle
linux/ppc64
linux/ppc64le
linux/riscv64
linux/s390x
netbsd/386
netbsd/amd64
netbsd/arm
netbsd/arm64
openbsd/386
openbsd/amd64
openbsd/arm
openbsd/arm64
openbsd/ppc64
openbsd/riscv64
plan9/386
plan9/amd64
plan9/arm
solaris/amd64
wasip1/wasm
windows/386
windows/amd64
windows/arm
windows/arm64
</code></pre>
<blockquote>
<p>注：像sock_bsd.go、sock_posix.go这样的Go源文件，虽然它们的文件名中包含posix、bsd等字样，但这些文件实际上只是普通的Go源文件。其文件名本身并不会影响Go包在构建时选择文件的结果。</p>
</blockquote>
<ul>
<li>调用matchTag来判定该Go源文件名字中的OS和ARCH是否与当前上下文信息中的OS和ARCH匹配</li>
</ul>
<p>Go支持的源文件名组成格式如下：</p>
<pre><code>  //  name_$(GOOS).*
  //  name_$(GOARCH).*
  //  name_$(GOOS)_$(GOARCH).*
  //  name_$(GOOS)_test.*
  //  name_$(GOARCH)_test.*
  //  name_$(GOOS)_$(GOARCH)_test.*
</code></pre>
<p>不过这里有三个例外，即：</p>
<p>如果上下文中的GOOS=android，那么文件名字中OS值为linux的Go源文件也算是匹配的；</p>
<p>如果上下文中的GOOS=illumos，那么文件名字中OS值为solaris的Go源文件也算是匹配的；</p>
<p>如果上下文中的GOOS=ios，那么文件名字中OS值为darwin的Go源文件也算是匹配的。</p>
<p>还有一个特殊处理，那就是当文件名字中OS值为unix时，该源文件可以匹配以下上下文中GOOS的值：</p>
<pre><code>// $GOROOT/src/go/build/syslist.go

// unixOS is the set of GOOS values matched by the "unix" build tag.
// This is not used for filename matching.
// This list also appears in cmd/dist/build.go and
// cmd/go/internal/imports/build.go.
var unixOS = map[string]bool{
    "aix":       true,
    "android":   true,
    "darwin":    true,
    "dragonfly": true,
    "freebsd":   true,
    "hurd":      true,
    "illumos":   true,
    "ios":       true,
    "linux":     true,
    "netbsd":    true,
    "openbsd":   true,
    "solaris":   true,
}
</code></pre>
<p>这里面列出os都是所谓的“类Unix”操作系统。</p>
<p>如果goodOSArchFile方法返回文件名匹配成功，那么<strong>第二步就是调用Context的shouldBuild方法对Go源文件中的build constraints进行判定</strong>，这个判定过程也是调用matchTag完成的，因此规则与上面对matchTag的说明一致。如果判定match成功，那么该源文件将会被Go编译器编译到最终的Go包目标文件中去。</p>
<p>下面我们结合文章第一节“表象”中的那个自定义示例来判定一下为何最终会输出那个结果。</p>
<h2>3. 示例分析</h2>
<p>在buildconstraints/demo1/foo包目录中，一共有三个Go源文件：</p>
<pre><code>$tree -F foo
foo
├── f1_android.go
├── f2_linux.go
└── f3_darwin.go
</code></pre>
<p>注意：当前我的系统为<strong>darwin/amd64</strong>，但我们使用了GOOS=android的环境变量。我们顺着上一节梳理出来的文件选择判定的主逻辑，对着三个文件逐一过一遍。</p>
<ul>
<li>f1_android.go</li>
</ul>
<p>首先用goodOSArchFile判定文件名是否匹配。当GOOS=android时，文件名中的os为android，文件名匹配成功，</p>
<p>然后用shouldBuild判定文件中的build constraints是否匹配。该文件的约束为linux，在上面matchTag的三个例外规则里提到过，当GOOS=android时，如果build constraints是linux，是可以匹配的。</p>
<p>因此，f1_android.go将出现在最终编译文件列表中。</p>
<ul>
<li>f2_linux.go</li>
</ul>
<p>首先用goodOSArchFile判定文件名是否匹配。当GOOS=android时，文件名中的os为linux，linux显然在go支持的os列表中，并且根据matchTag的例外规则，当GOOS=android时，文件名中的os为linux时是可以匹配的。</p>
<p>然后用shouldBuild判定文件中的build constraints是否匹配。该文件的约束为android，与GOOS相同，可以匹配。</p>
<p>因此，f2_linux.go将出现在最终编译文件列表中。</p>
<ul>
<li>f3_darwin.go</li>
</ul>
<p>首先用goodOSArchFile判定文件名是否匹配。当GOOS=android时，文件名中的os为darwin，虽然darwin在go支持的os列表中，但darwin与GOOS=android并不匹配，因此在goodOSArchFile这步中，f3_darwin.go就被“淘汰”掉了！即便f3_darwin.go中的build constraints为android。</p>
<p>因此，f3_darwin.go不会出现在最终编译文件列表中。</p>
<p>如果再增加一个源文件f4_unix.go，其内容为：</p>
<pre><code>//go:build android

func F4() {
}
</code></pre>
<p>这个f4_unix.go是否会出现在最终的包编译文件列表中呢？这个作为思考题留给大家了，也欢迎你在评论区留言，说说你的思考结果。</p>
<h2>4. 小结</h2>
<p>在Go语言的开发过程中，包的构建是核心环节之一，而源文件的选择则是构建过程中一个复杂且关键的细节。本文深入探讨了Go编译器在执行go build命令时，如何根据文件名中的架构（ARCH）和操作系统（OS）标识，以及构建约束（build constraints），来选择需要编译的源文件。</p>
<p>通过具体示例，本文展示了不同文件名和构建约束如何影响最终的编译结果，并揭示了Go编译器处理这些信息的优先级。理解这些内部机制不仅能帮助开发者优化构建过程，还能有效避免潜在的错误。希望本文的分析能够给大家带去帮助。</p>
<blockquote>
<p>注：限于篇幅，本文仅针对包编译文件选择最复杂的部分进行的探索，而像ReleaseTags(比如: go1.21等)、cgo、&#95;test.go后缀等比较明显的约束并未涉及，同时对于新版build constraints的运算符组合也未提及，感兴趣的童鞋可以参考<a href="https://pkg.go.dev/cmd/go#hdr-Build_constraints">go build constraints</a>官方文档查阅。</p>
</blockquote>
<p>本文涉及的源码可以在<a href="https://github.com/bigwhite/experiments/tree/master/buildconstraints">这里</a>下载。</p>
<h2>5. 参考资料</h2>
<ul>
<li><a href="https://pkg.go.dev/cmd/go#hdr-Build_constraints">Go build constraints</a> &#8211; https://pkg.go.dev/cmd/go#hdr-Build_constraints</li>
<li><a href="https://github.com/golang/go/issues/25348">proposal: cmd/go: allow &amp;&amp; and || operators and parentheses in build tags</a> &#8211; https://github.com/golang/go/issues/25348</li>
<li><a href="https://go.googlesource.com/proposal/+/master/design/draft-gobuild.md">Bug-resistant build constraints — Draft Design</a> &#8211; https://go.googlesource.com/proposal/+/master/design/draft-gobuild.md</li>
<li><a href="https://github.com/golang/go/issues/41184">cmd/go: continue conversion to bug-resistant //go:build constraints</a> &#8211; https://github.com/golang/go/issues/41184</li>
<li><a href="https://go.dev/doc/go1.17">Go 1.17 release notes</a> &#8211; https://go.dev/doc/go1.17</li>
<li><a href="https://github.com/golang/go/issues/45454">cmd/go: provide build tags for architecture environment variables</a> &#8211; https://github.com/golang/go/issues/45454</li>
</ul>
<hr />
<p><a href="https://public.zsxq.com/groups/51284458844544">Gopher部落知识星球</a>在2024年将继续致力于打造一个高品质的Go语言学习和交流平台。我们将继续提供优质的Go技术文章首发和阅读体验。同时，我们也会加强代码质量和最佳实践的分享，包括如何编写简洁、可读、可测试的Go代码。此外，我们还会加强星友之间的交流和互动。欢迎大家踊跃提问，分享心得，讨论技术。我会在第一时间进行解答和交流。我衷心希望Gopher部落可以成为大家学习、进步、交流的港湾。让我相聚在Gopher部落，享受coding的快乐! 欢迎大家踊跃加入！</p>
<p><img src="http://image.tonybai.com/img/tonybai/gopher-tribe-zsxq-small-card.png" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/go-programming-from-beginner-to-master-qr.png" alt="img{512x368}" /></p>
<p><img src="http://image.tonybai.com/img/tonybai/go-first-course-banner.png" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/imooc-go-column-pgo-with-qr.jpg" alt="img{512x368}" /></p>
<p>著名云主机服务厂商DigitalOcean发布最新的主机计划，入门级Droplet配置升级为：1 core CPU、1G内存、25G高速SSD，价格5$/月。有使用DigitalOcean需求的朋友，可以打开这个<a href="https://m.do.co/c/bff6eed92687">链接地址</a>：https://m.do.co/c/bff6eed92687 开启你的DO主机之路。</p>
<p>Gopher Daily(Gopher每日新闻) &#8211; https://gopherdaily.tonybai.com</p>
<p>我的联系方式：</p>
<ul>
<li>微博(暂不可用)：https://weibo.com/bigwhite20xx</li>
<li>微博2：https://weibo.com/u/6484441286</li>
<li>博客：tonybai.com</li>
<li>github: https://github.com/bigwhite</li>
<li>Gopher Daily归档 &#8211; https://github.com/bigwhite/gopherdaily</li>
<li>Gopher Daily Feed订阅 &#8211; https://gopherdaily.tonybai.com/feed</li>
</ul>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。</p>
<p style='text-align:left'>&copy; 2024, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2024/11/21/go-source-file-selection-details-when-building-package/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Go 1.16新功能特性不完全前瞻</title>
		<link>https://tonybai.com/2020/12/12/a-forward-look-to-new-feature-of-go-1-16/</link>
		<comments>https://tonybai.com/2020/12/12/a-forward-look-to-new-feature-of-go-1-16/#comments</comments>
		<pubDate>Sat, 12 Dec 2020 03:37:41 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[arm64]]></category>
		<category><![CDATA[bson]]></category>
		<category><![CDATA[Cgo]]></category>
		<category><![CDATA[Darwin]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[go.mod]]></category>
		<category><![CDATA[go1.15]]></category>
		<category><![CDATA[go1.16]]></category>
		<category><![CDATA[go:embed]]></category>
		<category><![CDATA[GOARCH]]></category>
		<category><![CDATA[GODEBUG]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[gomodule]]></category>
		<category><![CDATA[GOOS]]></category>
		<category><![CDATA[Gopher部落]]></category>
		<category><![CDATA[init]]></category>
		<category><![CDATA[iOS]]></category>
		<category><![CDATA[json]]></category>
		<category><![CDATA[linker]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[M1]]></category>
		<category><![CDATA[Macbook]]></category>
		<category><![CDATA[metrics]]></category>
		<category><![CDATA[retract]]></category>
		<category><![CDATA[RISC-V]]></category>
		<category><![CDATA[runtime]]></category>
		<category><![CDATA[structtag]]></category>
		<category><![CDATA[Unicode]]></category>
		<category><![CDATA[XML]]></category>
		<category><![CDATA[知识星球]]></category>
		<category><![CDATA[结构体]]></category>
		<category><![CDATA[运行时]]></category>
		<category><![CDATA[链接器]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=3026</guid>
		<description><![CDATA[2020年最后一个购物狂欢，双十二购物节“Gopher部落”知识星球推出双十二优惠！本年度最低折扣仅限今天一天。笔者建立“Gopher部落”旨在建立一个高质量的Go语言技术精品社区，持续不断的高质量技术资料分享，让加入的星友每天都有新收获！欢迎大家加入！ Go 1.16将于2021年2月发布。目前已经进入freeze状态，即不再接受新feature，仅fix bug、编写文档和接受安全更新等。 目前Go 1.16的发布说明尚处于早期草稿阶段，但Go团队成员正在致力于编写发布说明。Go 1.16的完全特性列表说明还得等真正发布前才能得到。如今要了解Go 1.16功能特性都有哪些变化，只能结合现有的release note以及从Go 1.16里程碑中的issue列表中挖掘。 下面就“挖掘”到的Go 1.16重要功能特性变化做简要的且不完全的前瞻。 1. 支持Apple Silicon M1芯片 Apple Silicon M1芯片Macbook的发布让Go团队紧急为Go 1.16增加对M1的支持。如果要跨平台编译，只需设定GOOS=darwin, GOARCH=arm64即可构建出可以在搭载M1芯片的Macbook上运行的Go应用。 同时Go 1.16还增加了对ios/amd64的支持，主要是为了支持在amd64架构上的MacOS上运行ios模拟器。 2. RISC-V架构支持cgo和-buildmode=pie RISC-V架构很可能是未来5-10年挑战ARM的最主要架构，Go语言持续加大对RISC-V架构的支持，在Go 1.16中对linux/riscv64又增加了cgo支持以及-buildmode=pie。不过目前对risc-v仍仅限于linux os。 3. 有关go module的变化 module-aware模式成为默认状态。如要回到gopath mode，将GO111MODULE设置为auto； go build和go test不会修改go.mod和go.sum文件。能修改这两个文件的命令只有go get和go mod tidy； go get之前的构建和安装go包的行为模式将被废弃。go get将专注于分析依赖，并获取go包/module，更新go.mod/go.sum； go install将恢复自己构建和安装包的“角色”（在go module加入后，go install日益受到冷落，这次翻身了)； go.mod将支持retract指示符，包或module作者可以利用该指示符在自己module的go.mod中标记某些版本撤回(因不安全、不兼容或损坏等原因)，不建议使用。 go.mod中的exclude指示符语义变更：Go 1.16中将忽略exclude指示的module/包依赖；而之前的版本go工具链仅仅是跳过exclude指示的版本，而使用该依赖包/module的下一个版本。 -i build flag废弃； go get的-insecure命令行标志选项作废，可以用GOINSECURE环境变量指示go get是否通过不安全的http去获取包； [...]]]></description>
			<content:encoded><![CDATA[<p>2020年最后一个购物狂欢，<a href="http://image.tonybai.com/img/202011/gopher-tribe-2020-12-12.png">双十二购物节“Gopher部落”知识星球推出双十二优惠！</a>本年度最低折扣仅限今天一天。笔者建立“Gopher部落”旨在建立一个高质量的Go语言技术精品社区，持续不断的高质量技术资料分享，让加入的星友每天都有新收获！欢迎大家加入！</p>
<p><img src="http://image.tonybai.com/img/202011/gopher-tribe-2020-12-12.png" alt="" /></p>
<p>Go 1.16将于2021年2月发布。目前已经进入freeze状态，即不再接受新feature，仅fix bug、编写文档和接受安全更新等。</p>
<p><img src="https://tonybai.com/wp-content/uploads/go-1.16.png" alt="img{512x368}" /></p>
<p>目前Go 1.16的<a href="https://tip.golang.org/doc/go1.16">发布说明</a>尚处于早期草稿阶段，但Go团队成员正在致力于编写发布说明。Go 1.16的完全特性列表说明还得等真正发布前才能得到。如今要了解Go 1.16功能特性都有哪些变化，只能结合现有的release note以及从<a href="https://github.com/golang/go/issues?q=is%3Aissue+milestone%3AGo1.16+is%3Aclosed">Go 1.16里程碑</a>中的issue列表中挖掘。</p>
<p>下面就“挖掘”到的Go 1.16重要功能特性变化做简要的且不完全的前瞻。</p>
<h3>1. 支持Apple Silicon M1芯片</h3>
<p>Apple Silicon M1芯片Macbook的发布让Go团队紧急为Go 1.16增加对M1的支持。如果要跨平台编译，只需设定GOOS=darwin, GOARCH=arm64即可构建出可以在搭载M1芯片的Macbook上运行的Go应用。</p>
<p>同时Go 1.16还增加了对ios/amd64的支持，主要是为了支持在amd64架构上的MacOS上运行ios模拟器。</p>
<h3>2. RISC-V架构支持cgo和-buildmode=pie</h3>
<p>RISC-V架构很可能是未来5-10年挑战ARM的最主要架构，Go语言持续加大对RISC-V架构的支持，在Go 1.16中对linux/riscv64又增加了cgo支持以及-buildmode=pie。不过目前对risc-v仍仅限于linux os。</p>
<h3>3. 有关go module的变化</h3>
<ul>
<li>module-aware模式成为默认状态。如要回到gopath mode，将GO111MODULE设置为auto；</li>
<li>go build和go test不会修改go.mod和go.sum文件。能修改这两个文件的命令只有go get和go mod tidy；</li>
<li>go get之前的构建和安装go包的行为模式将被废弃。go get将专注于分析依赖，并获取go包/module，更新go.mod/go.sum；</li>
<li>go install将恢复自己构建和安装包的“角色”（在go module加入后，go install日益受到冷落，这次翻身了)；</li>
<li><a href="https://github.com/golang/go/issues/24031">go.mod将支持retract指示符</a>，包或module作者可以利用该指示符在自己module的go.mod中标记某些版本撤回(因不安全、不兼容或损坏等原因)，不建议使用。</li>
<li>go.mod中的exclude指示符语义变更：Go 1.16中将忽略exclude指示的module/包依赖；而之前的版本go工具链仅仅是跳过exclude指示的版本，而使用该依赖包/module的下一个版本。</li>
<li>-i build flag废弃；</li>
<li>go get的-insecure命令行标志选项作废，可以用GOINSECURE环境变量指示go get是否通过不安全的http去获取包；</li>
</ul>
<h3>4. 支持在Go二进制文件中嵌入静态文件(文本、图片等)</h3>
<p>Go 1.16新增<strong>go:embed</strong>指示符和embed标准库包，二者一起用于支持在在Go二进制文件中嵌入静态文件。下面是一个在Go应用中嵌入文本文件用于http应答内容的小例子：</p>
<pre><code>// hello.txt
hello, go 1.16

// main.go
package main

import (
         _  "embed"
    "net/http"
)

//go:embed hello.txt
var s string

func main() {
    http.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte(s))
    }))
    http.ListenAndServe(":8080", nil)
}
</code></pre>
<p>上述源码中的<strong>go:embed</strong>指示符的含义是：将hello.txt内容存储在字符串变量s中。我们构建该源码，并验证一下s中存储的是否是hello.txt中的数据：</p>
<pre><code>$ go build -o demo main.go
$ mv hello.txt hello.txt.bak // 将hello.txt改名，我们看看数据是否真的已经嵌入到二进制文件demo中了
$ ./demo

$curl localhost:8080
hello, go 1.16
</code></pre>
<h3>5.GODEBUG环境变量支持跟踪</h3>
<p>当GODEBUG环境变量包含inittrace=1时，Go运行时将会报告各个源代码文件中的init函数的执行时间和内存开辟消耗情况。比如对于上面的程序demo，我们按如下命令执行：</p>
<pre><code># GODEBUG=inittrace=1 ./demo
init internal/bytealg @0.014 ms, 0 ms clock, 0 bytes, 0 allocs
init runtime @0.033 ms, 0.015 ms clock, 0 bytes, 0 allocs
init errors @0.24 ms, 0.003 ms clock, 0 bytes, 0 allocs
init sync @0.47 ms, 0.001 ms clock, 16 bytes, 1 allocs
init io @0.66 ms, 0 ms clock, 144 bytes, 9 allocs
init internal/oserror @0.85 ms, 0 ms clock, 80 bytes, 5 allocs
init syscall @1.0 ms, 0.006 ms clock, 624 bytes, 2 allocs
init time @1.2 ms, 0.013 ms clock, 384 bytes, 8 allocs
init path @1.4 ms, 0.003 ms clock, 16 bytes, 1 allocs
init io/fs @1.6 ms, 0 ms clock, 16 bytes, 1 allocs
init context @2.3 ms, 0.002 ms clock, 128 bytes, 4 allocs
init math @2.5 ms, 0 ms clock, 0 bytes, 0 allocs
init strconv @2.7 ms, 0 ms clock, 32 bytes, 2 allocs
init unicode @2.9 ms, 0.065 ms clock, 23736 bytes, 26 allocs
init bytes @3.2 ms, 0 ms clock, 48 bytes, 3 allocs
init crypto @3.3 ms, 0.001 ms clock, 160 bytes, 1 allocs
init reflect @3.5 ms, 0.002 ms clock, 0 bytes, 0 allocs
init encoding/binary @3.7 ms, 0 ms clock, 16 bytes, 1 allocs
init crypto/cipher @3.8 ms, 0 ms clock, 16 bytes, 1 allocs
init crypto/aes @4.0 ms, 0.003 ms clock, 16 bytes, 1 allocs
init internal/poll @4.1 ms, 0 ms clock, 64 bytes, 4 allocs
init os @4.2 ms, 0.029 ms clock, 544 bytes, 13 allocs
init fmt @4.4 ms, 0.003 ms clock, 32 bytes, 2 allocs
init math/rand @4.5 ms, 0.023 ms clock, 5440 bytes, 3 allocs
init math/big @4.7 ms, 0.002 ms clock, 32 bytes, 2 allocs
init crypto/sha512 @4.8 ms, 0.004 ms clock, 0 bytes, 0 allocs
init encoding/asn1 @5.0 ms, 0.004 ms clock, 224 bytes, 7 allocs
init vendor/golang.org/x/crypto/cryptobyte @5.1 ms, 0 ms clock, 48 bytes, 2 allocs
init crypto/ecdsa @5.3 ms, 0 ms clock, 48 bytes, 3 allocs
init bufio @5.4 ms, 0.003 ms clock, 176 bytes, 11 allocs
init crypto/rand @5.6 ms, 0.001 ms clock, 120 bytes, 4 allocs
init crypto/rsa @5.7 ms, 0.007 ms clock, 648 bytes, 18 allocs
init crypto/sha1 @5.8 ms, 0 ms clock, 0 bytes, 0 allocs
init crypto/sha256 @5.9 ms, 0 ms clock, 0 bytes, 0 allocs
init encoding/base64 @5.9 ms, 0.006 ms clock, 1408 bytes, 4 allocs
init crypto/md5 @6.0 ms, 0 ms clock, 0 bytes, 0 allocs
init encoding/hex @6.1 ms, 0 ms clock, 16 bytes, 1 allocs
init crypto/x509/pkix @6.1 ms, 0.001 ms clock, 624 bytes, 2 allocs
init path/filepath @6.2 ms, 0 ms clock, 16 bytes, 1 allocs
init vendor/golang.org/x/net/dns/dnsmessage @6.3 ms, 0.009 ms clock, 1616 bytes, 27 allocs
init net @6.3 ms, 0.029 ms clock, 2840 bytes, 74 allocs
init crypto/dsa @6.5 ms, 0 ms clock, 16 bytes, 1 allocs
init crypto/x509 @6.5 ms, 0.016 ms clock, 4768 bytes, 15 allocs
init io/ioutil @6.7 ms, 0.002 ms clock, 16 bytes, 1 allocs
init vendor/golang.org/x/sys/cpu @6.7 ms, 0.009 ms clock, 1280 bytes, 1 allocs
init vendor/golang.org/x/crypto/chacha20poly1305 @6.8 ms, 0 ms clock, 16 bytes, 1 allocs
init vendor/golang.org/x/crypto/curve25519 @6.9 ms, 0 ms clock, 0 bytes, 0 allocs
init crypto/tls @7.0 ms, 0.007 ms clock, 1600 bytes, 11 allocs
init log @7.0 ms, 0 ms clock, 80 bytes, 1 allocs
init mime @7.1 ms, 0.008 ms clock, 1232 bytes, 4 allocs
init mime/multipart @7.2 ms, 0.001 ms clock, 192 bytes, 4 allocs
init compress/flate @7.3 ms, 0.012 ms clock, 4240 bytes, 7 allocs
init hash/crc32 @7.4 ms, 0.014 ms clock, 1024 bytes, 1 allocs
init compress/gzip @7.5 ms, 0 ms clock, 32 bytes, 2 allocs
init vendor/golang.org/x/text/transform @7.5 ms, 0 ms clock, 80 bytes, 5 allocs
init vendor/golang.org/x/text/unicode/bidi @7.6 ms, 0.005 ms clock, 272 bytes, 2 allocs
init vendor/golang.org/x/text/secure/bidirule @7.7 ms, 0.008 ms clock, 16 bytes, 1 allocs
init vendor/golang.org/x/text/unicode/norm @7.8 ms, 0.002 ms clock, 0 bytes, 0 allocs
init vendor/golang.org/x/net/idna @7.8 ms, 0 ms clock, 0 bytes, 0 allocs
init vendor/golang.org/x/net/http/httpguts @7.9 ms, 0.002 ms clock, 848 bytes, 3 allocs
init vendor/golang.org/x/net/http2/hpack @7.9 ms, 0.063 ms clock, 22440 bytes, 32 allocs
init net/http/internal @8.1 ms, 0.005 ms clock, 1808 bytes, 3 allocs
init vendor/golang.org/x/net/http/httpproxy @8.2 ms, 0 ms clock, 336 bytes, 2 allocs
init net/http @8.3 ms, 0.026 ms clock, 10280 bytes, 113 allocs
</code></pre>
<p>我们看到各个依赖包中的init函数执行的消耗情况都被输出了出来，根据这些信息，我们可以很容易判断出init函数中可能存在的性能问题或瓶颈。</p>
<h3>6. 链接器进一步优化</h3>
<p>既<a href="https://mp.weixin.qq.com/s/B5onfyP7BPYCh_rMSBtfcQ">Go 1.15</a>实现了go linker的第一阶段优化后，Go 1.16中继续实施了对linker的第二阶段优化。优化后的链接器要平均比Go 1.15的快20%-25%，消耗的内存却减少5%-15%。</p>
<h3>7. struct field的tag中的多个key可以合并写</h3>
<p>如果某个结构体支持多种编码格式的序列化和反序列化，比如：json、bson、xml，那么之前版本需要按如下书写该结构体的字段tag，冗长且重复：</p>
<pre><code>type MyStruct struct {
  Field1 string `json:"field_1,omitempty" bson:"field_1,omitempty" xml:"field_1,omitempty" form:"field_1,omitempty" other:"value"`
}
</code></pre>
<p><a href="https://github.com/golang/go/issues/40281">Go 1.16支持将多个key进行合并</a>，上面的tag可以写成如下形式：</p>
<pre><code>type MyStruct struct {
  Field1 string `json bson xml form:"field_1,omitempty" other:"value"`
}
</code></pre>
<h3>8. 其他改变</h3>
<ul>
<li>新增runtime/metrics包，以替代runtime.ReadMemStats和debug.ReadGCStats输出runtime的各种度量数据，这个包更通用稳定，性能也更好；</li>
<li>新增io/fs包，用于提供只读的操作os的文件树的高级接口；</li>
<li>对Unicode标准的支持从12.0.0升级为13.0.0。 </li>
</ul>
<h3>附录：安装go tip版本的两种方式</h3>
<h4>1) 从源码安装</h4>
<pre><code>$git clone https//github.com/golang/go.git
$cd go/src
$./all.bash
</code></pre>
<h4>2) 使用gotip工具安装</h4>
<pre><code>$go get golang.org/dl/gotip
$gotip download
</code></pre>
<hr />
<p>我的Go技术专栏：“<a href="https://www.imooc.com/read/87">改善Go语⾔编程质量的50个有效实践</a>”上线了，欢迎大家订阅学习！</p>
<p><img src="https://tonybai.com/wp-content/uploads/go-column-pgo-with-qr-and-text.png" alt="img{512x368}" /></p>
<p>我的网课“<a href="https://coding.imooc.com/class/284.html">Kubernetes实战：高可用集群搭建、配置、运维与应用</a>”在慕课网热卖中，欢迎小伙伴们订阅学习！</p>
<p><img src="https://tonybai.com/wp-content/uploads/k8s-practice-with-qr-and-text.png" alt="img{512x368}" /></p>
<p><a href="https://tonybai.com/">我爱发短信</a>：企业级短信平台定制开发专家 https://tonybai.com/<br />
smspush : 可部署在企业内部的定制化短信平台，三网覆盖，不惧大并发接入，可定制扩展； 短信内容你来定，不再受约束, 接口丰富，支持长短信，签名可选。</p>
<p>2020年4月8日，中国三大电信运营商联合发布《5G消息白皮书》，51短信平台也会全新升级到“51商用消息平台”，全面支持5G RCS消息。</p>
<p>著名云主机服务厂商DigitalOcean发布最新的主机计划，入门级Droplet配置升级为：1 core CPU、1G内存、25G高速SSD，价格5$/月。有使用DigitalOcean需求的朋友，可以打开这个<a href="https://m.do.co/c/bff6eed92687">链接地址</a>：https://m.do.co/c/bff6eed92687 开启你的DO主机之路。</p>
<p>Gopher Daily(Gopher每日新闻)归档仓库 &#8211; https://github.com/bigwhite/gopherdaily</p>
<p>我的联系方式：</p>
<ul>
<li>微博：https://weibo.com/bigwhite20xx</li>
<li>微信公众号：iamtonybai</li>
<li>博客：tonybai.com</li>
<li>github: https://github.com/bigwhite</li>
<li>“Gopher部落”知识星球：https://public.zsxq.com/groups/51284458844544</li>
</ul>
<p>微信赞赏：<br />
<img src="https://tonybai.com/wp-content/uploads/wechat-zanshang-code-512x512.jpg" alt="img{512x368}" /></p>
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。</p>
<p style='text-align:left'>&copy; 2020, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2020/12/12/a-forward-look-to-new-feature-of-go-1-16/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Go 1.15中值得关注的几个变化</title>
		<link>https://tonybai.com/2020/10/11/some-changes-in-go-1-15/</link>
		<comments>https://tonybai.com/2020/10/11/some-changes-in-go-1-15/#comments</comments>
		<pubDate>Sun, 11 Oct 2020 03:10:06 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[arm64]]></category>
		<category><![CDATA[cache]]></category>
		<category><![CDATA[Darwin]]></category>
		<category><![CDATA[GNU]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[go1.11]]></category>
		<category><![CDATA[go1.13]]></category>
		<category><![CDATA[go1.14]]></category>
		<category><![CDATA[go1.15]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[gomodule]]></category>
		<category><![CDATA[GOPATH]]></category>
		<category><![CDATA[GOPROXY]]></category>
		<category><![CDATA[GOROOT]]></category>
		<category><![CDATA[Interface]]></category>
		<category><![CDATA[json]]></category>
		<category><![CDATA[linker]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Module]]></category>
		<category><![CDATA[objdump]]></category>
		<category><![CDATA[Package]]></category>
		<category><![CDATA[panic]]></category>
		<category><![CDATA[Reflect]]></category>
		<category><![CDATA[RISC-V]]></category>
		<category><![CDATA[tzdata]]></category>
		<category><![CDATA[unsafe]]></category>
		<category><![CDATA[Windows]]></category>
		<category><![CDATA[zoneinfo]]></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=2955</guid>
		<description><![CDATA[Go 1.15版本在8月12日就正式发布了，给我的感觉就是发布的挺痛快^_^。这种感觉来自与之前版本发布时间的对比：Go 1.13版本发布于当年的9月4日，更早的Go 1.11版本发布于当年的8月25日。 不过这个时间恰与我家二宝出生和老婆月子时期有重叠，每天照顾孩子团团转的我实在抽不出时间研究Go 1.15的变化:(。如今，我逐渐从照顾二宝的工作中脱离出来^_^，于是“Go x.xx版本值得关注的几个变化”系列将继续下去。关注Go语言的演变对掌握和精通Go语言大有裨益，凡是致力于成为一名高级Gopher的读者都应该密切关注Go的演进。 截至写稿时，Go 1.15最新版是Go 1.15.2。Go 1.15一如既往的遵循Go1兼容性承诺。语言规范方面没有任何变化。可以说这是一个“面子”上变化较小的一个版本，但“里子”的变化还是不少的，在本文中我就和各位读者一起就重要变化逐一了解一下。 一. 平台移植性 Go 1.15版本不再对darwin/386和darwin/arm两个32位平台提供支持了。Go 1.15及以后版本仅对darwin/amd64和darwin/arm64版本提供支持。并且不再对macOS 10.12版本之前的版本提供支持。 Go 1.14版本中，Go编译器在被传入-race和-msan的情况下，默认会执行-d=checkptr，即对unsafe.Pointer的使用进行合法性检查。-d=checkptr主要检查两项内容： 当将unsafe.Pointer转型为&#42;T时，T的内存对齐系数不能高于原地址的； 做完指针算术后，转换后的unsafe.Pointer仍应指向原先Go堆对象 但在Go 1.14中，这个检查并不适用于Windows操作系统。Go 1.15中增加了对windows系统的支持。 对于RISC-V架构，Go社区展现出十分积极的姿态，早在Go 1.11版本，Go就为RISC-V cpu架构预留了GOARCH值：riscv和riscv64。Go 1.14版本则为64bit RISC-V提供了在linux上的实验性支持(GOOS=linux, GOARCH=riscv64)。在Go 1.15版本中，Go在GOOS=linux, GOARCH=riscv64的环境下的稳定性和性能得到持续提升，并且已经可以支持goroutine异步抢占式调度了。 二. 工具链 1. GOPROXY新增以管道符为分隔符的代理列表值 在Go 1.13版本中，GOPROXY支持设置为多个proxy的列表，多个proxy之间采用逗号分隔。Go工具链会按顺序尝试列表中的proxy以获取依赖包数据，但是当有proxy server服务不可达或者是返回的http状态码不是404也不是410时，go会终止数据获取。但是当列表中的proxy server返回其他错误时，Go命令不会向GOPROXY列表中的下一个值所代表的的proxy server发起请求，这种行为模式没能让所有gopher满意，很多Gopher认为Go工具链应该向后面的proxy server请求，直到所有proxy server都返回失败。Go 1.15版本满足了Go社区的需求，新增以管道符“&#124;”为分隔符的代理列表值。如果GOPROXY配置的proxy server列表值以管道符分隔，则无论某个proxy server返回什么错误码，Go命令都会向列表中的下一个proxy server发起新的尝试请求。 注：Go 1.15版本中GOPROXY环境变量的默认值依旧为https://proxy.golang.org,direct。 2. module cache的存储路径可设置 Go module机制自打在Go 1.11版本中以试验特性的方式引入时就将module的本地缓存默认放在了\$GOPATH/pkg/mod下（如果没有显式设置GOPATH，那么默认值将是~/go；如果GOPATH下面配置了多个路径，那么选择第一个路径），一直到Go [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/go-1.15-1.png" alt="img{512x368}" /></p>
<p><a href="https://tip.golang.org/doc/go1.15">Go 1.15版本</a>在8月12日就正式发布了，给我的感觉就是发布的挺痛快^_^。这种感觉来自与之前版本发布时间的对比：<a href="https://tonybai.com/2019/10/27/some-changes-in-go-1-13/">Go 1.13版本</a>发布于当年的9月4日，更早的<a href="https://tonybai.com/2018/11/19/some-changes-in-go-1-11/">Go 1.11版本</a>发布于当年的8月25日。</p>
<p>不过这个时间恰与我家<a href="https://tonybai.com/2020/07/29/my-second-daughter-was-born/">二宝出生</a>和老婆月子时期有重叠，每天照顾孩子团团转的我实在抽不出时间研究Go 1.15的变化:(。如今，我逐渐从照顾二宝的工作中脱离出来^_^，于是“Go x.xx版本值得关注的几个变化”系列将继续下去。关注Go语言的演变对掌握和精通Go语言大有裨益，凡是致力于成为一名高级Gopher的读者都应该密切关注Go的演进。<br />
截至写稿时，Go 1.15最新版是Go 1.15.2。Go 1.15一如既往的遵循<a href="https://tip.golang.org/doc/go1compat.html">Go1兼容性承诺</a>。<a href="https://tip.golang.org/ref/spec">语言规范</a>方面没有任何变化。可以说这是一个“面子”上变化较小的一个版本，但“里子”的变化还是不少的，在本文中我就和各位读者一起就重要变化逐一了解一下。</p>
<h3>一. 平台移植性</h3>
<p>Go 1.15版本不再对darwin/386和darwin/arm两个32位平台提供支持了。Go 1.15及以后版本仅对darwin/amd64和darwin/arm64版本提供支持。并且不再对macOS 10.12版本之前的版本提供支持。</p>
<p><a href="https://tonybai.com/2020/03/08/some-changes-in-go-1-14/">Go 1.14版本</a>中，Go编译器在被传入-race和-msan的情况下，默认会执行<strong>-d=checkptr</strong>，即对unsafe.Pointer的使用进行<a href="https://github.com/golang/go/issues/34964">合法性检查</a>。<strong>-d=checkptr</strong>主要检查两项内容：</p>
<ul>
<li>
<p>当将unsafe.Pointer转型为&#42;T时，T的内存对齐系数不能高于原地址的；</p>
</li>
<li>
<p>做完指针算术后，转换后的unsafe.Pointer仍应指向原先Go堆对象</p>
</li>
</ul>
<p>但在Go 1.14中，这个检查并不适用于Windows操作系统。Go 1.15中增加了对windows系统的支持。</p>
<p>对于<a href="https://riscv.org">RISC-V</a>架构，Go社区展现出十分积极的姿态，早在<a href="https://tonybai.com/2018/11/19/some-changes-in-go-1-11/">Go 1.11版本</a>，Go就为RISC-V cpu架构预留了GOARCH值：riscv和riscv64。<a href="https://tonybai.com/2020/03/08/some-changes-in-go-1-14/">Go 1.14版本</a>则为64bit RISC-V提供了在linux上的实验性支持(GOOS=linux, GOARCH=riscv64)。在Go 1.15版本中，Go在GOOS=linux, GOARCH=riscv64的环境下的稳定性和性能得到持续提升，并且已经可以支持goroutine异步抢占式调度了。</p>
<h3>二. 工具链</h3>
<h4>1. GOPROXY新增以管道符为分隔符的代理列表值</h4>
<p>在<a href="https://tonybai.com/2019/10/27/some-changes-in-go-1-13/">Go 1.13版本</a>中，<a href="https://tonybai.com/2018/11/26/hello-go-module-proxy/">GOPROXY</a>支持设置为多个proxy的列表，多个proxy之间采用逗号分隔。Go工具链会按顺序尝试列表中的proxy以获取依赖包数据，但是当有proxy server服务不可达或者是返回的http状态码不是404也不是410时，go会终止数据获取。但是当列表中的proxy server返回其他错误时，Go命令不会向GOPROXY列表中的下一个值所代表的的proxy server发起请求，这种行为模式没能让所有gopher满意，<strong>很多Gopher认为Go工具链应该向后面的proxy server请求，直到所有proxy server都返回失败</strong>。Go 1.15版本满足了Go社区的需求，新增以管道符“|”为分隔符的代理列表值。如果GOPROXY配置的proxy server列表值以管道符分隔，则无论某个proxy server返回什么错误码，Go命令都会向列表中的下一个proxy server发起新的尝试请求。</p>
<blockquote>
<p>注：Go 1.15版本中GOPROXY环境变量的默认值依旧为<strong>https://proxy.golang.org,direct</strong>。</p>
</blockquote>
<h4>2. module cache的存储路径可设置</h4>
<p><a href="https://tonybai.com/2019/06/03/the-practice-of-upgrading-major-version-under-go-module/">Go module机制</a>自打在<a href="https://tonybai.com/2018/11/19/some-changes-in-go-1-11/">Go 1.11版本</a>中以试验特性的方式引入时就将module的本地缓存默认放在了<strong>\$GOPATH/pkg/mod</strong>下（如果没有显式设置GOPATH，那么默认值将是<strong>~/go</strong>；如果GOPATH下面配置了多个路径，那么选择第一个路径），一直到Go 1.14版本，这个位置都是无法配置的。</p>
<p>Go module的引入为去除GOPATH提供了前提，于是module cache的位置也要尽量与GOPATH“脱钩”：Go 1.15提供了GOMODCACHE环境变量用于自定义module cache的存放位置。如果没有显式设置GOMODCACHE，那么module cache的默认存储路径依然是<strong>\$GOPATH/pkg/mod</strong>。</p>
<h3>三. 运行时、编译器和链接器</h3>
<h4>1. panic展现形式变化</h4>
<p>在Go 1.15之前，如果传给panic的值是bool, complex64, complex128, float32, float64, int, int8, int16, int32, int64, string, uint, uint8, uint16, uint32, uint64, uintptr等原生类型的值，那么panic在触发时会输出具体的值，比如：</p>
<pre><code>// go1.15-examples/runtime/panic.go

package main

func foo() {
    var i uint32 = 17
    panic(i)
}

func main() {
    foo()
}
</code></pre>
<p>使用Go 1.14运行上述代码，得到如下结果：</p>
<pre><code>$go run panic.go
panic: 17

goroutine 1 [running]:
main.foo(...)
    /Users/tonybai/go/src/github.com/bigwhite/experiments/go1.15-examples/runtime/panic.go:5
main.main()
    /Users/tonybai/go/src/github.com/bigwhite/experiments/go1.15-examples/runtime/panic.go:9 +0x39
exit status 2
</code></pre>
<p>Go 1.15版本亦是如此。但是对于派生于上述原生类型的自定义类型而言，Go 1.14只是输出变量地址：</p>
<pre><code>// go1.15-examples/runtime/panic.go

package main

type myint uint32

func bar() {
    var i myint = 27
    panic(i)
}

func main() {
    bar()
}
</code></pre>
<p>使用Go 1.14运行上述代码：</p>
<pre><code>$go run panic.go
panic: (main.myint) (0x105fca0,0xc00008e000)

goroutine 1 [running]:
main.bar(...)
    /Users/tonybai/go/src/github.com/bigwhite/experiments/go1.15-examples/runtime/panic.go:12
main.main()
    /Users/tonybai/go/src/github.com/bigwhite/experiments/go1.15-examples/runtime/panic.go:17 +0x39
exit status 2

</code></pre>
<p>Go 1.15针对此情况作了展示优化，即便是派生于这些原生类型的自定义类型变量，panic也可以输出其值。使用Go 1.15运行上述代码的结果如下：</p>
<pre><code>$go run panic.go
panic: main.myint(27)

goroutine 1 [running]:
main.bar(...)
    /Users/tonybai/go/src/github.com/bigwhite/experiments/go1.15-examples/runtime/panic.go:12
main.main()
    /Users/tonybai/go/src/github.com/bigwhite/experiments/go1.15-examples/runtime/panic.go:17 +0x39
exit status 2

</code></pre>
<h4>2. 将小整数([0,255])转换为interface类型值时将不会额外分配内存</h4>
<p>Go 1.15在runtime/iface.go中做了一些优化改动：增加一个名为staticuint64s的数组，预先为[0,255]这256个数分配了内存。然后在convT16、convT32等运行时转换函数中判断要转换的整型值是否小于256(len(staticuint64s))，如果小于，则返回staticuint64s数组中对应的值的地址；否则调用mallocgc分配新内存。</p>
<pre><code>$GOROOT/src/runtime/iface.go

// staticuint64s is used to avoid allocating in convTx for small integer values.
var staticuint64s = [...]uint64{
        0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
        0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
        0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
        0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
        0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
        0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
        0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,

        ... ...

        0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
        0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff,

}

func convT16(val uint16) (x unsafe.Pointer) {
        if val &lt; uint16(len(staticuint64s)) {
                x = unsafe.Pointer(&amp;staticuint64s[val])
                if sys.BigEndian {
                        x = add(x, 6)
                }
        } else {
                x = mallocgc(2, uint16Type, false)
                *(*uint16)(x) = val
        }
        return
}

func convT32(val uint32) (x unsafe.Pointer) {
        if val &lt; uint32(len(staticuint64s)) {
                x = unsafe.Pointer(&amp;staticuint64s[val])
                if sys.BigEndian {
                        x = add(x, 4)
                }
        } else {
                x = mallocgc(4, uint32Type, false)
                *(*uint32)(x) = val
        }
        return
}

</code></pre>
<p>我们可以用下面例子来验证一下：</p>
<pre><code>// go1.15-examples/runtime/tinyint2interface.go

package main

import (
    "math/rand"
)

func convertSmallInteger() interface{} {
    i := rand.Intn(256)
    var j interface{} = i
    return j
}

func main() {
    for i := 0; i &lt; 100000000; i++ {
        convertSmallInteger()
    }
}

</code></pre>
<p>我们分别用go 1.14和go 1.15.2编译这个源文件（注意关闭内联等优化，否则很可能看不出效果）：</p>
<pre><code>// go 1.14

go build  -gcflags="-N -l" -o tinyint2interface-go14 tinyint2interface.go

// go 1.15.2

go build  -gcflags="-N -l" -o tinyint2interface-go15 tinyint2interface.go

</code></pre>
<p>我们使用下面命令输出程序执行时每次GC的信息：</p>
<pre><code>$env GODEBUG=gctrace=1 ./tinyint2interface-go14
gc 1 @0.025s 0%: 0.009+0.18+0.021 ms clock, 0.079+0.079/0/0.20+0.17 ms cpu, 4-&gt;4-&gt;0 MB, 5 MB goal, 8 P
gc 2 @0.047s 0%: 0.003+0.14+0.013 ms clock, 0.031+0.099/0.064/0.037+0.10 ms cpu, 4-&gt;4-&gt;0 MB, 5 MB goal, 8 P
gc 3 @0.064s 0%: 0.008+0.20+0.016 ms clock, 0.071+0.071/0.018/0.081+0.13 ms cpu, 4-&gt;4-&gt;0 MB, 5 MB goal, 8 P
gc 4 @0.081s 0%: 0.005+0.14+0.013 ms clock, 0.047+0.059/0.023/0.032+0.10 ms cpu, 4-&gt;4-&gt;0 MB, 5 MB goal, 8 P
gc 5 @0.098s 0%: 0.005+0.10+0.017 ms clock, 0.042+0.073/0.027/0.080+0.13 ms cpu, 4-&gt;4-&gt;0 MB, 5 MB goal, 8 P

... ...

gc 192 @3.264s 0%: 0.003+0.11+0.013 ms clock, 0.024+0.060/0.005/0.035+0.11 ms cpu, 4-&gt;4-&gt;0 MB, 5 MB goal, 8 P
gc 193 @3.281s 0%: 0.005+0.13+0.032 ms clock, 0.042+0.075/0.041/0.050+0.25 ms cpu, 4-&gt;4-&gt;0 MB, 5 MB goal, 8 P
gc 194 @3.298s 0%: 0.004+0.12+0.013 ms clock, 0.033+0.072/0.030/0.033+0.10 ms cpu, 4-&gt;4-&gt;0 MB, 5 MB goal, 8 P
gc 195 @3.315s 0%: 0.003+0.17+0.023 ms clock, 0.029+0.062/0.055/0.024+0.18 ms cpu, 4-&gt;4-&gt;0 MB, 5 MB goal, 8 P

$env GODEBUG=gctrace=1 ./tinyint2interface-go15
</code></pre>
<p>我们看到和go 1.14编译的程序不断分配内存，不断导致GC相比，go1.15.2没有输出GC信息，间接证实了小整数转interface变量值时不会触发内存分配。</p>
<h4>3. 加入更现代化的链接器(linker)</h4>
<p>一个新版的<a href="https://golang.org/s/better-linker">现代化linker</a>正在逐渐加入到Go中，Go 1.15是新版linker的起点。后续若干版本，linker优化会逐步加入进来。在Go 1.15中，对于大型项目，新链接器的性能要提高20%，内存占用减少30%。</p>
<h4>4. objdump支持输出GNU汇编语法</h4>
<p>go 1.15为objdump工具增加了-gnu选项，<strong>以在Go汇编的后面，辅助输出GNU汇编，便于对照</strong>：</p>
<pre><code>// go 1.14：

$go tool objdump -S tinyint2interface-go15|more
TEXT go.buildid(SB)

  0x1001000             ff20                    JMP 0(AX)
  0x1001002             476f                    OUTSD DS:0(SI), DX
  0x1001004             206275                  ANDB AH, 0x75(DX)
  0x1001007             696c642049443a20        IMULL $0x203a4449, 0x20(SP), BP
... ...

//go 1.15.2：

$go tool objdump  -S -gnu tinyint2interface-go15|more
TEXT go.buildid(SB)

  0x1001000             ff20                    JMP 0(AX)                            // jmpq *(%rax)           

  0x1001002             476f                    OUTSD DS:0(SI), DX                   // rex.RXB outsl %ds:(%rsi),(%dx)
  0x1001004             206275                  ANDB AH, 0x75(DX)                    // and %ah,0x75(%rdx)     

  0x1001007             696c642049443a20        IMULL $0x203a4449, 0x20(SP), BP      // imul $0x203a4449,0x20(%rsp,%riz,2),%ebp

... ...

</code></pre>
<h3>四. 标准库</h3>
<p>和以往发布的版本一样，标准库有大量小改动，这里挑出几个笔者感兴趣的和大家一起看一下。</p>
<h4>1. 增加tzdata包</h4>
<p>Go time包中很多方法依赖时区数据，但不是所有平台上都自带时区数据。Go time包会以下面顺序搜寻时区数据：</p>
<pre><code>- ZONEINFO环境变量指示的路径中

- 在类Unix系统中一些常见的存放时区数据的路径（zoneinfo_unix.go中的zoneSources数组变量中存放这些常见路径）：

    "/usr/share/zoneinfo/",
    "/usr/share/lib/zoneinfo/",
    "/usr/lib/locale/TZ/"

- 如果平台没有，则尝试使用$GOROOT/lib/time/zoneinfo.zip这个随着go发布包一起发布的时区数据。但在应用部署的环境中，很大可能不会进行go安装。

</code></pre>
<p>如果go应用找不到时区数据，那么go应用运行将会受到影响，就如下面这个例子：</p>
<pre><code>// go1.15-examples/stdlib/tzdata.go

package main

import (
    "fmt"
    "time"
)

func main() {
    loc, err := time.LoadLocation("America/New_York")
    if err != nil {
        fmt.Println("LoadLocation error:", err)
        return
    }
    fmt.Println("LoadLocation is:", loc)
}

</code></pre>
<p>我们移除系统的时区数据(比如将/usr/share/zoneinfo改名)和Go安装包自带的zoneinfo.zip(改个名)后，在Go 1.15.2下运行该示例：</p>
<pre><code>$ go run tzdata.go
LoadLocation error: unknown time zone America/New_York

</code></pre>
<p>为此，Go 1.15提供了一个将时区数据嵌入到Go应用二进制文件中的方法：<strong>导入time/tzdata包</strong>：</p>
<pre><code>// go1.15-examples/stdlib/tzdata.go

package main

import (
    "fmt"
    "time"
    _ "time/tzdata"
)

func main() {
    loc, err := time.LoadLocation("America/New_York")
    if err != nil {
        fmt.Println("LoadLocation error:", err)
        return
    }
    fmt.Println("LoadLocation is:", loc)
}

</code></pre>
<p>我们再用go 1.15.2运行一下上述导入tzdata包的例子：</p>
<pre><code>$go run testtimezone.go
LoadLocation is: America/New_York

</code></pre>
<p>不过由于附带tzdata数据，应用二进制文件的size会增大大约800k，下面是在ubuntu下的实测值：</p>
<pre><code>-rwxr-xr-x 1 root root 2.0M Oct 11 02:42 tzdata-withouttzdata*
-rwxr-xr-x 1 root root 2.8M Oct 11 02:42 tzdata-withtzdata*

</code></pre>
<h4>2. 增加json解码限制</h4>
<p>json包是日常使用最多的go标准库包之一，在Go 1.15中，go按照json规范的要求，为json的解码增加了一层限制：</p>
<pre><code>// json规范要求

//https://tools.ietf.org/html/rfc7159#section-9

A JSON parser transforms a JSON text into another representation.  A
   JSON parser MUST accept all texts that conform to the JSON grammar.
   A JSON parser MAY accept non-JSON forms or extensions.

   An implementation may set limits on the size of texts that it
   accepts.  An implementation may set limits on the maximum depth of
   nesting.  An implementation may set limits on the range and precision
   of numbers.  An implementation may set limits on the length and
   character contents of strings.

</code></pre>
<p>这个限制就是增加了一个对json文本最大缩进深度值：</p>
<pre><code>// $GOROOT/src/encoding/json/scanner.go

// This limits the max nesting depth to prevent stack overflow.
// This is permitted by https://tools.ietf.org/html/rfc7159#section-9
const maxNestingDepth = 10000
</code></pre>
<p>如果一旦传入的json文本数据缩进深度超过maxNestingDepth，那json包就会panic。当然，绝大多数情况下，我们是碰不到缩进10000层的超大json文本的。因此，该limit对于99.9999%的gopher都没啥影响。</p>
<h4>3. reflect包</h4>
<p>Go 1.15版本之前reflect包<a href="https://github.com/golang/go/issues/38521">存在一处行为不一致的问题</a>，我们看下面例子(例子来源于https://play.golang.org/p/Jnga2_6Rmdf)：</p>
<pre><code>// go1.15-examples/stdlib/reflect.go

package main

import "reflect"

type u struct{}

func (u) M() { println("M") }

type t struct {
    u
    u2 u
}

func call(v reflect.Value) {
    defer func() {
        if err := recover(); err != nil {
            println(err.(string))
        }
    }()
    v.Method(0).Call(nil)
}

func main() {
    v := reflect.ValueOf(t{}) // v := t{}
    call(v)                   // v.M()
    call(v.Field(0))          // v.u.M()
    call(v.Field(1))          // v.u2.M()
}

</code></pre>
<p>我们使用Go 1.14版本运行该示例：</p>
<pre><code>$go run reflect.go
M
M
reflect: reflect.flag.mustBeExported using value obtained using unexported field

</code></pre>
<p>我们看到同为类型t中的非导出字段(field)的u和u2(u是以嵌入类型方式称为类型t的字段的)，通过reflect包可以调用字段u的导出方法(如输出中的第二行的M)，却无法调用非导出字段u2的导出方法（如输出中的第三行的panic信息）。</p>
<p>这种不一致在Go 1.15版本中被修复，我们使用Go 1.15.2运行上述示例：</p>
<pre><code>$go run reflect.go
M
reflect: reflect.Value.Call using value obtained using unexported field
reflect: reflect.Value.Call using value obtained using unexported field

</code></pre>
<p>我们看到reflect无法调用非导出字段u和u2的导出方法了。但是reflect依然可以通过提升到类型t的方法来间接使用u的导出方法，正如运行结果中的第一行输出。<br />
<strong>这一改动可能会影响到遗留代码中使用reflect调用以类型嵌入形式存在的非导出字段方法的代码</strong>，如果你的代码中存在这样的问题，可以直接通过提升(promote)到包裹类型(如例子中的t)中的方法（如例子中的call(v)）来替代之前的方式。</p>
<h3>五. 小结</h3>
<p>由于Go 1.15删除了一些GC元数据和一些无用的类型元数据，Go 1.15编译出的二进制文件size会减少5%左右。我用一个中等规模的go项目实测了一下：</p>
<pre><code>-rwxr-xr-x   1 tonybai  staff    23M 10 10 16:54 yunxind*
-rwxr-xr-x   1 tonybai  staff    24M  9 30 11:20 yunxind-go14*

</code></pre>
<p>二进制文件size的确有变小，大约4%-5%。</p>
<p><strong>如果你还没有升级到Go 1.15，那么现在正是时候</strong>。</p>
<p>本文中涉及的代码可以在<a href="https://github.com/bigwhite/experiments/tree/master/go1.15-examples">这里</a>下载。https://github.com/bigwhite/experiments/tree/master/go1.15-examples</p>
<hr />
<p>我的Go技术专栏：“<a href="https://www.imooc.com/read/87">改善Go语⾔编程质量的50个有效实践</a>”上线了，欢迎大家订阅学习！</p>
<p>我的网课“<a href="https://coding.imooc.com/class/284.html">Kubernetes实战：高可用集群搭建、配置、运维与应用</a>”在慕课网上线了，感谢小伙伴们学习支持！</p>
<p><a href="https://tonybai.com/">我爱发短信</a>：企业级短信平台定制开发专家 https://tonybai.com/<br />
smspush : 可部署在企业内部的定制化短信平台，三网覆盖，不惧大并发接入，可定制扩展； 短信内容你来定，不再受约束, 接口丰富，支持长短信，签名可选。</p>
<p>2020年4月8日，中国三大电信运营商联合发布《5G消息白皮书》，51短信平台也会全新升级到“51商用消息平台”，全面支持5G RCS消息。</p>
<p>著名云主机服务厂商DigitalOcean发布最新的主机计划，入门级Droplet配置升级为：1 core CPU、1G内存、25G高速SSD，价格5$/月。有使用DigitalOcean需求的朋友，可以打开这个<a href="https://m.do.co/c/bff6eed92687">链接地址</a>：https://m.do.co/c/bff6eed92687 开启你的DO主机之路。</p>
<p>Gopher Daily(Gopher每日新闻)归档仓库 &#8211; https://github.com/bigwhite/gopherdaily</p>
<p>我的联系方式：</p>
<p>微博：https://weibo.com/bigwhite20xx<br />
微信公众号：iamtonybai<br />
博客：tonybai.com<br />
github: https://github.com/bigwhite</p>
<p>微信赞赏：<br />
<img src="https://tonybai.com/wp-content/uploads/wechat-zanshang-code-512x512.jpg" alt="img{512x368}" /></p>
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。</p>
<p style='text-align:left'>&copy; 2020, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2020/10/11/some-changes-in-go-1-15/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>Go语言TCP Socket编程</title>
		<link>https://tonybai.com/2015/11/17/tcp-programming-in-golang/</link>
		<comments>https://tonybai.com/2015/11/17/tcp-programming-in-golang/#comments</comments>
		<pubDate>Tue, 17 Nov 2015 09:27:11 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Block]]></category>
		<category><![CDATA[Client]]></category>
		<category><![CDATA[Darwin]]></category>
		<category><![CDATA[epoll]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[goroutine]]></category>
		<category><![CDATA[IO]]></category>
		<category><![CDATA[kqueue]]></category>
		<category><![CDATA[libev]]></category>
		<category><![CDATA[Libevent]]></category>
		<category><![CDATA[libuv]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[lock]]></category>
		<category><![CDATA[MacOSX]]></category>
		<category><![CDATA[Mutex]]></category>
		<category><![CDATA[netpoller]]></category>
		<category><![CDATA[Non-Block]]></category>
		<category><![CDATA[Poll]]></category>
		<category><![CDATA[runtime]]></category>
		<category><![CDATA[Scheduler]]></category>
		<category><![CDATA[select]]></category>
		<category><![CDATA[Server]]></category>
		<category><![CDATA[Solaris]]></category>
		<category><![CDATA[TCP]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<category><![CDATA[Unix]]></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>
		<category><![CDATA[锁]]></category>
		<category><![CDATA[阻塞]]></category>
		<category><![CDATA[非阻塞]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=1907</guid>
		<description><![CDATA[Golang的主要 设计目标之一就是面向大规模后端服务程序，网络通信这块是服务端 程序必不可少也是至关重要的一部分。在日常应用中，我们也可以看到Go中的net以及其subdirectories下的包均是“高频+刚需”，而TCP socket则是网络编程的主流，即便您没有直接使用到net中有关TCP Socket方面的接口，但net/http总是用到了吧，http底层依旧是用tcp socket实现的。 网络编程方面，我们最常用的就是tcp socket编程了，在posix标准出来后，socket在各大主流OS平台上都得到了很好的支持。关于tcp programming，最好的资料莫过于W. Richard Stevens 的网络编程圣经《UNIX网络 编程 卷1：套接字联网API》 了，书中关于tcp socket接口的各种使用、行为模式、异常处理讲解的十分细致。Go是自带runtime的跨平台编程语言，Go中暴露给语言使用者的tcp socket api是建立OS原生tcp socket接口之上的。由于Go runtime调度的需要，golang tcp socket接口在行为特点与异常处理方面与OS原生接口有着一些差别。这篇博文的目标就是整理出关于Go tcp socket在各个场景下的使用方法、行为特点以及注意事项。 一、模型 从tcp socket诞生后，网络编程架构模型也几经演化，大致是：“每进程一个连接” &#8211;> “每线程一个连接” &#8211;> “Non-Block + I/O多路复用(linux epoll/windows iocp/freebsd darwin kqueue/solaris Event Port)”。伴随着模型的演化，服务程序愈加强大，可以支持更多的连接，获得更好的处理性能。 目前主流web server一般均采用的都是”Non-Block + I/O多路复用”（有的也结合了多线程、多进程）。不过I/O多路复用也给使用者带来了不小的复杂度，以至于后续出现了许多高性能的I/O多路复用框架， 比如libevent、libev、libuv等，以帮助开发者简化开发复杂性，降低心智负担。不过Go的设计者似乎认为I/O多路复用的这种通过回调机制割裂控制流 的方式依旧复杂，且有悖于“一般逻辑”设计，为此Go语言将该“复杂性”隐藏在Runtime中了：Go开发者无需关注socket是否是 non-block的，也无需亲自注册文件描述符的回调，只需在每个连接对应的goroutine中以“block I/O”的方式对待socket处理即可，这可以说大大降低了开发人员的心智负担。一个典型的Go server端程序大致如下： //go-tcpsock/server.go func handleConn(c net.Conn) { defer c.Close() [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://tonybai.com/tag/go">Golang</a>的主要 设计目标之一就是面向大规模后端服务程序，网络通信这块是服务端 程序必不可少也是至关重要的一部分。在日常应用中，我们也可以看到Go中的net以及其subdirectories下的包均是“高频+刚需”，而TCP socket则是网络编程的主流，即便您没有直接使用到net中有关TCP Socket方面的接口，但net/http总是用到了吧，http底层依旧是用tcp socket实现的。</p>
<p>网络编程方面，我们最常用的就是tcp socket编程了，在posix标准出来后，socket在各大主流OS平台上都得到了很好的支持。关于tcp programming，最好的资料莫过于<a href="http://en.wikipedia.org/wiki/W._Richard_Stevens">W. Richard Stevens</a> 的网络编程圣经《<a href="http://book.douban.com/subject/4859464/">UNIX网络 编程 卷1：套接字联网API</a>》 了，书中关于tcp socket接口的各种使用、行为模式、异常处理讲解的十分细致。Go是自带runtime的跨平台编程语言，Go中暴露给语言使用者的tcp socket api是建立OS原生tcp socket接口之上的。由于Go runtime调度的需要，golang tcp socket接口在行为特点与异常处理方面与OS原生接口有着一些差别。这篇博文的目标就是整理出关于Go tcp socket在各个场景下的使用方法、行为特点以及注意事项。</p>
<h3>一、模型</h3>
<p>从tcp socket诞生后，网络编程架构模型也几经演化，大致是：“每进程一个连接”  &#8211;>  “每线程一个连接”  &#8211;>  “Non-Block + I/O多路复用(linux epoll/windows iocp/freebsd darwin kqueue/solaris Event Port)”。伴随着模型的演化，服务程序愈加强大，可以支持更多的连接，获得更好的处理性能。</p>
<p>目前主流web server一般均采用的都是”Non-Block + I/O多路复用”（有的也结合了多线程、多进程）。不过I/O多路复用也给使用者带来了不小的复杂度，以至于后续出现了许多高性能的I/O多路复用框架， 比如<a href="http://libevent.org/">libevent</a>、<a href="http://software.schmorp.de/pkg/libev.html">libev</a>、<a href="https://github.com/joyent/libuv">libuv</a>等，以帮助开发者简化开发复杂性，降低心智负担。不过Go的设计者似乎认为I/O多路复用的这种通过回调机制割裂控制流 的方式依旧复杂，且有悖于“一般逻辑”设计，为此Go语言将该“复杂性”隐藏在Runtime中了：Go开发者无需关注socket是否是 non-block的，也无需亲自注册文件描述符的回调，只需在每个连接对应的goroutine中以<strong>“block I/O”</strong>的方式对待socket处理即可，这可以说大大降低了开发人员的心智负担。一个典型的Go server端程序大致如下：</p>
<pre><code>//go-tcpsock/server.go
func handleConn(c net.Conn) {
    defer c.Close()
    for {
        // read from the connection
        // ... ...
        // write to the connection
        //... ...
    }
}

func main() {
    l, err := net.Listen("tcp", ":8888")
    if err != nil {
        fmt.Println("listen error:", err)
        return
    }

    for {
        c, err := l.Accept()
        if err != nil {
            fmt.Println("accept error:", err)
            break
        }
        // start a new goroutine to handle
        // the new connection.
        go handleConn(c)
    }
}
</code></pre>
<p>用户层眼中看到的goroutine中的“block socket”，实际上是通过Go runtime中的netpoller通过Non-block socket + I/O多路复用机制“模拟”出来的，真实的underlying socket实际上是non-block的，只是runtime拦截了底层socket系统调用的错误码，并通过netpoller和goroutine 调度让goroutine“阻塞”在用户层得到的Socket fd上。比如：当用户层针对某个socket fd发起read操作时，如果该socket fd中尚无数据，那么runtime会将该socket fd加入到netpoller中监听，同时对应的goroutine被挂起，直到runtime收到socket fd 数据ready的通知，runtime才会重新唤醒等待在该socket fd上准备read的那个Goroutine。而这个过程从Goroutine的视角来看，就像是read操作一直block在那个socket fd上似的。具体实现细节在后续场景中会有补充描述。</p>
<h3>二、TCP连接的建立</h3>
<p>众所周知，TCP Socket的连接的建立需要经历客户端和服务端的三次握手的过程。连接建立过程中，服务端是一个标准的Listen + Accept的结构(可参考上面的代码)，而在客户端Go语言使用net.Dial或DialTimeout进行连接建立：</p>
<p>阻塞Dial：</p>
<pre><code>conn, err := net.Dial("tcp", "google.com:80")
if err != nil {
    //handle error
}
// read or write on conn
</code></pre>
<p>或是带上超时机制的Dial：</p>
<pre><code>conn, err := net.DialTimeout("tcp", ":8080", 2 * time.Second)
if err != nil {
    //handle error
}
// read or write on conn

</code></pre>
<p>对于客户端而言，连接的建立会遇到如下几种情形：</p>
<hr />
<h4>1、网络不可达或对方服务未启动</h4>
<p>如果传给Dial的Addr是可以立即判断出网络不可达，或者Addr中端口对应的服务没有启动，端口未被监听，Dial会几乎立即返回错误，比如：</p>
<pre><code>//go-tcpsock/conn_establish/client1.go
... ...
func main() {
    log.Println("begin dial...")
    conn, err := net.Dial("tcp", ":8888")
    if err != nil {
        log.Println("dial error:", err)
        return
    }
    defer conn.Close()
    log.Println("dial ok")
}
</code></pre>
<p>如果本机8888端口未有服务程序监听，那么执行上面程序，Dial会很快返回错误：</p>
<pre><code>$go run client1.go
2015/11/16 14:37:41 begin dial...
2015/11/16 14:37:41 dial error: dial tcp :8888: getsockopt: connection refused
</code></pre>
<h4>2、对方服务的listen backlog满</h4>
<p>还有一种场景就是对方服务器很忙，瞬间有大量client端连接尝试向server建立，server端的listen backlog队列满，server accept不及时((即便不accept，那么在backlog数量范畴里面，connect都会是成功的，因为new conn已经加入到server side的listen queue中了，accept只是从queue中取出一个conn而已)，这将导致client端Dial阻塞。我们还是通过例子感受Dial的行为特点：</p>
<p>服务端代码：</p>
<pre><code>//go-tcpsock/conn_establish/server2.go
... ...
func main() {
    l, err := net.Listen("tcp", ":8888")
    if err != nil {
        log.Println("error listen:", err)
        return
    }
    defer l.Close()
    log.Println("listen ok")

    var i int
    for {
        time.Sleep(time.Second * 10)
        if _, err := l.Accept(); err != nil {
            log.Println("accept error:", err)
            break
        }
        i++
        log.Printf("%d: accept a new connection\n", i)
    }
}
</code></pre>
<p>客户端代码：</p>
<pre><code>//go-tcpsock/conn_establish/client2.go
... ...
func establishConn(i int) net.Conn {
    conn, err := net.Dial("tcp", ":8888")
    if err != nil {
        log.Printf("%d: dial error: %s", i, err)
        return nil
    }
    log.Println(i, ":connect to server ok")
    return conn
}

func main() {
    var sl []net.Conn
    for i := 1; i &lt; 1000; i++ {
        conn := establishConn(i)
        if conn != nil {
            sl = append(sl, conn)
        }
    }

    time.Sleep(time.Second * 10000)
}
</code></pre>
<p>从程序可以看出，服务端在listen成功后，每隔10s钟accept一次。客户端则是串行的尝试建立连接。这两个程序在Darwin下的执行 结果：</p>
<pre><code>$go run server2.go
2015/11/16 21:55:41 listen ok
2015/11/16 21:55:51 1: accept a new connection
2015/11/16 21:56:01 2: accept a new connection
... ...

$go run client2.go
2015/11/16 21:55:44 1 :connect to server ok
2015/11/16 21:55:44 2 :connect to server ok
2015/11/16 21:55:44 3 :connect to server ok
... ...

2015/11/16 21:55:44 126 :connect to server ok
2015/11/16 21:55:44 127 :connect to server ok
2015/11/16 21:55:44 128 :connect to server ok

2015/11/16 21:55:52 129 :connect to server ok
2015/11/16 21:56:03 130 :connect to server ok
2015/11/16 21:56:14 131 :connect to server ok
... ...
</code></pre>
<p>可以看出Client初始时成功地一次性建立了128个连接，然后后续每阻塞近10s才能成功建立一条连接。也就是说在server端 backlog满时(未及时accept)，客户端将阻塞在Dial上，直到server端进行一次accept。至于为什么是128，这与darwin 下的默认设置有关：</p>
<pre><code>$sysctl -a|grep kern.ipc.somaxconn
kern.ipc.somaxconn: 128
</code></pre>
<p>如果我在ubuntu 14.04上运行上述server程序，我们的client端初始可以成功建立499条连接。</p>
<p>如果server一直不accept，client端会一直阻塞么？我们去掉accept后的结果是：在Darwin下，client端会阻塞大 约1分多钟才会返回timeout：</p>
<pre><code>2015/11/16 22:03:31 128 :connect to server ok
2015/11/16 22:04:48 129: dial error: dial tcp :8888: getsockopt: operation timed out
</code></pre>
<p>而如果server运行在ubuntu 14.04上，client似乎一直阻塞，我等了10多分钟依旧没有返回。 阻塞与否看来与server端的网络实现和设置有关。</p>
<h4>3、网络延迟较大，Dial阻塞并超时</h4>
<p>如果网络延迟较大，TCP握手过程将更加艰难坎坷（各种丢包），时间消耗的自然也会更长。Dial这时会阻塞，如果长时间依旧无法建立连接，则Dial也会返回“ getsockopt: operation timed out”错误。</p>
<hr />
<p>在连接建立阶段，多数情况下，Dial是可以满足需求的，即便阻塞一小会儿。但对于某些程序而言，需要有严格的连接时间限定，如果一定时间内没能成功建立连接，程序可能会需要执行一段“异常”处理逻辑，为此我们就需要DialTimeout了。下面的例子将Dial的最长阻塞时间限制在2s内，超出这个时长，Dial将返回timeout error：</p>
<pre><code>//go-tcpsock/conn_establish/client3.go
... ...
func main() {
    log.Println("begin dial...")
    conn, err := net.DialTimeout("tcp", "104.236.176.96:80", 2*time.Second)
    if err != nil {
        log.Println("dial error:", err)
        return
    }
    defer conn.Close()
    log.Println("dial ok")
}

</code></pre>
<p>执行结果如下（需要模拟一个延迟较大的网络环境）：</p>
<pre><code>$go run client3.go
2015/11/17 09:28:34 begin dial...
2015/11/17 09:28:36 dial error: dial tcp 104.236.176.96:80: i/o timeout
</code></pre>
<h3>三、Socket读写</h3>
<p>连接建立起来后，我们就要在conn上进行读写，以完成业务逻辑。前面说过Go runtime隐藏了I/O多路复用的复杂性。语言使用者只需采用goroutine+Block I/O的模式即可满足大部分场景需求。Dial成功后，方法返回一个net.Conn接口类型变量值，这个接口变量的动态类型为一个*TCPConn：</p>
<pre><code>//$GOROOT/src/net/tcpsock_posix.go
type TCPConn struct {
    conn
}
</code></pre>
<p>TCPConn内嵌了一个unexported类型：conn，因此TCPConn”继承”了conn的Read和Write方法，后续通过Dial返回值调用的Write和Read方法均是net.conn的方法：</p>
<pre><code>//$GOROOT/src/net/net.go
type conn struct {
    fd *netFD
}

func (c *conn) ok() bool { return c != nil &amp;&amp; c.fd != nil }

// Implementation of the Conn interface.

// Read implements the Conn Read method.
func (c *conn) Read(b []byte) (int, error) {
    if !c.ok() {
        return 0, syscall.EINVAL
    }
    n, err := c.fd.Read(b)
    if err != nil &amp;&amp; err != io.EOF {
        err = &amp;OpError{Op: "read", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err}
    }
    return n, err
}

// Write implements the Conn Write method.
func (c *conn) Write(b []byte) (int, error) {
    if !c.ok() {
        return 0, syscall.EINVAL
    }
    n, err := c.fd.Write(b)
    if err != nil {
        err = &amp;OpError{Op: "write", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err}
    }
    return n, err
}
</code></pre>
<p>下面我们先来通过几个场景来总结一下conn.Read的行为特点。</p>
<hr />
<h4>1、Socket中无数据</h4>
<p>连接建立后，如果对方未发送数据到socket，接收方(Server)会阻塞在Read操作上，这和前面提到的“模型”原理是一致的。执行该Read操作的goroutine也会被挂起。runtime会监视该socket，直到其有数据才会重新<br />
调度该socket对应的Goroutine完成read。由于篇幅原因，这里就不列代码了，例子对应的代码文件：go-tcpsock/read_write下的client1.go和server1.go。</p>
<h4>2、Socket中有部分数据</h4>
<p>如果socket中有部分数据，且长度小于一次Read操作所期望读出的数据长度，那么Read将会成功读出这部分数据并返回，而不是等待所有期望数据全部读取后再返回。</p>
<p>Client端：</p>
<pre><code>//go-tcpsock/read_write/client2.go
... ...
func main() {
    if len(os.Args) &lt;= 1 {
        fmt.Println("usage: go run client2.go YOUR_CONTENT")
        return
    }
    log.Println("begin dial...")
    conn, err := net.Dial("tcp", ":8888")
    if err != nil {
        log.Println("dial error:", err)
        return
    }
    defer conn.Close()
    log.Println("dial ok")

    time.Sleep(time.Second * 2)
    data := os.Args[1]
    conn.Write([]byte(data))

    time.Sleep(time.Second * 10000)
}

</code></pre>
<p>Server端：</p>
<pre><code>//go-tcpsock/read_write/server2.go
... ...
func handleConn(c net.Conn) {
    defer c.Close()
    for {
        // read from the connection
        var buf = make([]byte, 10)
        log.Println("start to read from conn")
        n, err := c.Read(buf)
        if err != nil {
            log.Println("conn read error:", err)
            return
        }
        log.Printf("read %d bytes, content is %s\n", n, string(buf[:n]))
    }
}
... ...

</code></pre>
<p>我们通过client2.go发送”hi”到Server端：<br />
运行结果:</p>
<pre><code>$go run client2.go hi
2015/11/17 13:30:53 begin dial...
2015/11/17 13:30:53 dial ok

$go run server2.go
2015/11/17 13:33:45 accept a new connection
2015/11/17 13:33:45 start to read from conn
2015/11/17 13:33:47 read 2 bytes, content is hi
...
</code></pre>
<p>Client向socket中写入两个字节数据(“hi”)，Server端创建一个len = 10的slice，等待Read将读取的数据放入slice；Server随后读取到那两个字节：”hi”。Read成功返回，n =2 ，err = nil。</p>
<h4>3、Socket中有足够数据</h4>
<p>如果socket中有数据，且长度大于等于一次Read操作所期望读出的数据长度，那么Read将会成功读出这部分数据并返回。这个情景是最符合我们对Read的期待的了：Read将用Socket中的数据将我们传入的slice填满后返回：n = 10, err = nil。</p>
<p>我们通过client2.go向Server2发送如下内容：abcdefghij12345，执行结果如下：</p>
<pre><code>$go run client2.go abcdefghij12345
2015/11/17 13:38:00 begin dial...
2015/11/17 13:38:00 dial ok

$go run server2.go
2015/11/17 13:38:00 accept a new connection
2015/11/17 13:38:00 start to read from conn
2015/11/17 13:38:02 read 10 bytes, content is abcdefghij
2015/11/17 13:38:02 start to read from conn
2015/11/17 13:38:02 read 5 bytes, content is 12345
</code></pre>
<p>client端发送的内容长度为15个字节，Server端Read buffer的长度为10，因此Server Read第一次返回时只会读取10个字节；Socket中还剩余5个字节数据，Server再次Read时会把剩余数据读出（如：情形2）。</p>
<h4>4、Socket关闭</h4>
<p>如果client端主动关闭了socket，那么Server的Read将会读到什么呢？这里分为“有数据关闭”和“无数据关闭”。</p>
<p>“有数据关闭”是指在client关闭时，socket中还有server端未读取的数据，我们在go-tcpsock/read_write/client3.go和server3.go中模拟这种情况：</p>
<pre><code>$go run client3.go hello
2015/11/17 13:50:57 begin dial...
2015/11/17 13:50:57 dial ok

$go run server3.go
2015/11/17 13:50:57 accept a new connection
2015/11/17 13:51:07 start to read from conn
2015/11/17 13:51:07 read 5 bytes, content is hello
2015/11/17 13:51:17 start to read from conn
2015/11/17 13:51:17 conn read error: EOF
</code></pre>
<p>从输出结果来看，当client端close socket退出后，server3依旧没有开始Read，10s后第一次Read成功读出了5个字节的数据，当第二次Read时，由于client端 socket关闭，Read返回EOF error。</p>
<p>通过上面这个例子，我们也可以猜测出“无数据关闭”情形下的结果，那就是Read直接返回EOF error。</p>
<h4>5、读取操作超时</h4>
<p>有些场合对Read的阻塞时间有严格限制，在这种情况下，Read的行为到底是什么样的呢？在返回超时错误时，是否也同时Read了一部分数据了呢？这个实验比较难于模拟，下面的测试结果也未必能反映出所有可能结果。我们编写了client4.go和server4.go来模拟这一情形。</p>
<pre><code>//go-tcpsock/read_write/client4.go
... ...
func main() {
    log.Println("begin dial...")
    conn, err := net.Dial("tcp", ":8888")
    if err != nil {
        log.Println("dial error:", err)
        return
    }
    defer conn.Close()
    log.Println("dial ok")

    data := make([]byte, 65536)
    conn.Write(data)

    time.Sleep(time.Second * 10000)
}

//go-tcpsock/read_write/server4.go
... ...
func handleConn(c net.Conn) {
    defer c.Close()
    for {
        // read from the connection
        time.Sleep(10 * time.Second)
        var buf = make([]byte, 65536)
        log.Println("start to read from conn")
        c.SetReadDeadline(time.Now().Add(time.Microsecond * 10))
        n, err := c.Read(buf)
        if err != nil {
            log.Printf("conn read %d bytes,  error: %s", n, err)
            if nerr, ok := err.(net.Error); ok &amp;&amp; nerr.Timeout() {
                continue
            }
            return
        }
        log.Printf("read %d bytes, content is %s\n", n, string(buf[:n]))
    }
}

</code></pre>
<p>在Server端我们通过Conn的SetReadDeadline方法设置了10微秒的读超时时间，Server的执行结果如下：</p>
<pre><code>$go run server4.go

2015/11/17 14:21:17 accept a new connection
2015/11/17 14:21:27 start to read from conn
2015/11/17 14:21:27 conn read 0 bytes,  error: read tcp 127.0.0.1:8888-&gt;127.0.0.1:60970: i/o timeout
2015/11/17 14:21:37 start to read from conn
2015/11/17 14:21:37 read 65536 bytes, content is

</code></pre>
<p>虽然每次都是10微秒超时，但结果不同，第一次Read超时，读出数据长度为0；第二次读取所有数据成功，没有超时。反复执行了多次，没能出现“读出部分数据且返回超时错误”的情况。</p>
<hr />
<p>和读相比，Write遇到的情形一样不少，我们也逐一看一下。</p>
<hr />
<h4>1、成功写</h4>
<p>前面例子着重于Read，client端在Write时并未判断Write的返回值。所谓“成功写”指的就是Write调用返回的n与预期要写入的数据长度相等，且error = nil。这是我们在调用Write时遇到的最常见的情形，这里不再举例了。</p>
<h4>2、写阻塞</h4>
<p>TCP连接通信两端的OS都会为该连接保留数据缓冲，一端调用Write后，实际上数据是写入到OS的协议栈的数据缓冲的。TCP是全双工通信，因此每个方向都有独立的数据缓冲。当发送方将对方的接收缓冲区以及自身的发送缓冲区写满后，Write就会阻塞。我们来看一个例子：client5.go和server.go。</p>
<pre><code>//go-tcpsock/read_write/client5.go
... ...
func main() {
    log.Println("begin dial...")
    conn, err := net.Dial("tcp", ":8888")
    if err != nil {
        log.Println("dial error:", err)
        return
    }
    defer conn.Close()
    log.Println("dial ok")

    data := make([]byte, 65536)
    var total int
    for {
        n, err := conn.Write(data)
        if err != nil {
            total += n
            log.Printf("write %d bytes, error:%s\n", n, err)
            break
        }
        total += n
        log.Printf("write %d bytes this time, %d bytes in total\n", n, total)
    }

    log.Printf("write %d bytes in total\n", total)
    time.Sleep(time.Second * 10000)
}

//go-tcpsock/read_write/server5.go
... ...
func handleConn(c net.Conn) {
    defer c.Close()
    time.Sleep(time.Second * 10)
    for {
        // read from the connection
        time.Sleep(5 * time.Second)
        var buf = make([]byte, 60000)
        log.Println("start to read from conn")
        n, err := c.Read(buf)
        if err != nil {
            log.Printf("conn read %d bytes,  error: %s", n, err)
            if nerr, ok := err.(net.Error); ok &amp;&amp; nerr.Timeout() {
                continue
            }
        }

        log.Printf("read %d bytes, content is %s\n", n, string(buf[:n]))
    }
}
... ...
</code></pre>
<p>Server5在前10s中并不Read数据，因此当client5一直尝试写入时，写到一定量后就会发生阻塞：</p>
<pre><code>$go run client5.go

2015/11/17 14:57:33 begin dial...
2015/11/17 14:57:33 dial ok
2015/11/17 14:57:33 write 65536 bytes this time, 65536 bytes in total
2015/11/17 14:57:33 write 65536 bytes this time, 131072 bytes in total
2015/11/17 14:57:33 write 65536 bytes this time, 196608 bytes in total
2015/11/17 14:57:33 write 65536 bytes this time, 262144 bytes in total
2015/11/17 14:57:33 write 65536 bytes this time, 327680 bytes in total
2015/11/17 14:57:33 write 65536 bytes this time, 393216 bytes in total
2015/11/17 14:57:33 write 65536 bytes this time, 458752 bytes in total
2015/11/17 14:57:33 write 65536 bytes this time, 524288 bytes in total
2015/11/17 14:57:33 write 65536 bytes this time, 589824 bytes in total
2015/11/17 14:57:33 write 65536 bytes this time, 655360 bytes in total

</code></pre>
<p>在Darwin上，这个size大约在679468bytes。后续当server5每隔5s进行Read时，OS socket缓冲区腾出了空间，client5就又可以写入了：</p>
<pre><code>$go run server5.go
2015/11/17 15:07:01 accept a new connection
2015/11/17 15:07:16 start to read from conn
2015/11/17 15:07:16 read 60000 bytes, content is
2015/11/17 15:07:21 start to read from conn
2015/11/17 15:07:21 read 60000 bytes, content is
2015/11/17 15:07:26 start to read from conn
2015/11/17 15:07:26 read 60000 bytes, content is
....

client端：

2015/11/17 15:07:01 write 65536 bytes this time, 720896 bytes in total
2015/11/17 15:07:06 write 65536 bytes this time, 786432 bytes in total
2015/11/17 15:07:16 write 65536 bytes this time, 851968 bytes in total
2015/11/17 15:07:16 write 65536 bytes this time, 917504 bytes in total
2015/11/17 15:07:27 write 65536 bytes this time, 983040 bytes in total
2015/11/17 15:07:27 write 65536 bytes this time, 1048576 bytes in total
.... ...

</code></pre>
<h4>3、写入部分数据</h4>
<p>Write操作存在写入部分数据的情况，比如上面例子中，当client端输出日志停留在“write 65536 bytes this time, 655360 bytes in total”时，我们杀掉server5，这时我们会看到client5输出以下日志：</p>
<pre><code>...
2015/11/17 15:19:14 write 65536 bytes this time, 655360 bytes in total
2015/11/17 15:19:16 write 24108 bytes, error:write tcp 127.0.0.1:62245-&gt;127.0.0.1:8888: write: broken pipe
2015/11/17 15:19:16 write 679468 bytes in total
</code></pre>
<p>显然Write并非在655360这个地方阻塞的，而是后续又写入24108后发生了阻塞，server端socket关闭后，我们看到Wrote返回er != nil且n = 24108，程序需要对这部分写入的24108字节做特定处理。</p>
<h4>4、写入超时</h4>
<p>如果非要给Write增加一个期限，那我们可以调用SetWriteDeadline方法。我们copy一份client5.go，形成client6.go，在client6.go的Write之前增加一行timeout设置代码：</p>
<pre><code>conn.SetWriteDeadline(time.Now().Add(time.Microsecond * 10))
</code></pre>
<p>启动server6.go，启动client6.go，我们可以看到写入超时的情况下，Write的返回结果：</p>
<pre><code>$go run client6.go
2015/11/17 15:26:34 begin dial...
2015/11/17 15:26:34 dial ok
2015/11/17 15:26:34 write 65536 bytes this time, 65536 bytes in total
... ...
2015/11/17 15:26:34 write 65536 bytes this time, 655360 bytes in total
2015/11/17 15:26:34 write 24108 bytes, error:write tcp 127.0.0.1:62325-&gt;127.0.0.1:8888: i/o timeout
2015/11/17 15:26:34 write 679468 bytes in total
</code></pre>
<p>可以看到在写入超时时，依旧存在部分数据写入的情况。</p>
<hr />
<p>综上例子，虽然Go给我们提供了阻塞I/O的便利，但在调用Read和Write时依旧要综合需要方法返回的n和err的结果，以做出正确处理。net.conn实现了io.Reader和io.Writer接口，因此可以试用一些wrapper包进行socket读写，比如bufio包下面的Writer和Reader、io/ioutil下的函数等。</p>
<h4>Goroutine safe</h4>
<p>基于goroutine的网络架构模型，存在在不同goroutine间共享conn的情况，那么conn的读写是否是goroutine safe的呢？在深入这个问题之前，我们先从应用意义上来看read操作和write操作的goroutine-safe必要性。</p>
<p>对于read操作而言，由于TCP是面向字节流，conn.Read无法正确区分数据的业务边界，因此多个goroutine对同一个conn进行read的意义不大，goroutine读到不完整的业务包反倒是增加了业务处理的难度。对与Write操作而言，倒是有多个goroutine并发写的情况。不过conn读写是否goroutine-safe的测试不是很好做，我们先深入一下runtime代码，先从理论上给这个问题定个性：</p>
<p>net.conn只是*netFD的wrapper结构，最终Write和Read都会落在其中的fd上：</p>
<pre><code>type conn struct {
    fd *netFD
}
</code></pre>
<p>netFD在不同平台上有着不同的实现，我们以net/fd_unix.go中的netFD为例：</p>
<pre><code>// Network file descriptor.
type netFD struct {
    // locking/lifetime of sysfd + serialize access to Read and Write methods
    fdmu fdMutex

    // immutable until Close
    sysfd       int
    family      int
    sotype      int
    isConnected bool
    net         string
    laddr       Addr
    raddr       Addr

    // wait server
    pd pollDesc
}

</code></pre>
<p>我们看到netFD中包含了一个runtime实现的fdMutex类型字段，从注释上来看，该fdMutex用来串行化对该netFD对应的sysfd的Write和Read操作。从这个注释上来看，所有对conn的Read和Write操作都是有fdMutex互斥的，从netFD的Read和Write方法的实现也证实了这一点：</p>
<pre><code>func (fd *netFD) Read(p []byte) (n int, err error) {
    if err := fd.readLock(); err != nil {
        return 0, err
    }
    defer fd.readUnlock()
    if err := fd.pd.PrepareRead(); err != nil {
        return 0, err
    }
    for {
        n, err = syscall.Read(fd.sysfd, p)
        if err != nil {
            n = 0
            if err == syscall.EAGAIN {
                if err = fd.pd.WaitRead(); err == nil {
                    continue
                }
            }
        }
        err = fd.eofError(n, err)
        break
    }
    if _, ok := err.(syscall.Errno); ok {
        err = os.NewSyscallError("read", err)
    }
    return
}

func (fd *netFD) Write(p []byte) (nn int, err error) {
    if err := fd.writeLock(); err != nil {
        return 0, err
    }
    defer fd.writeUnlock()
    if err := fd.pd.PrepareWrite(); err != nil {
        return 0, err
    }
    for {
        var n int
        n, err = syscall.Write(fd.sysfd, p[nn:])
        if n &gt; 0 {
            nn += n
        }
        if nn == len(p) {
            break
        }
        if err == syscall.EAGAIN {
            if err = fd.pd.WaitWrite(); err == nil {
                continue
            }
        }
        if err != nil {
            break
        }
        if n == 0 {
            err = io.ErrUnexpectedEOF
            break
        }
    }
    if _, ok := err.(syscall.Errno); ok {
        err = os.NewSyscallError("write", err)
    }
    return nn, err
}
</code></pre>
<p>每次Write操作都是受lock保护，直到此次数据全部write完。因此在应用层面，要想保证多个goroutine在一个conn上write操作的Safe，需要一次write完整写入一个“业务包”；一旦将业务包的写入拆分为多次write，那就无法保证某个Goroutine的某“业务包”数据在conn发送的连续性。</p>
<p>同时也可以看出即便是Read操作，也是lock保护的。多个Goroutine对同一conn的并发读不会出现读出内容重叠的情况，但内容断点是依 runtime调度来随机确定的。存在一个业务包数据，1/3内容被goroutine-1读走，另外2/3被另外一个goroutine-2读 走的情况。比如一个完整包：world，当goroutine的read slice size &lt; 5时，存在可能：一个goroutine读到 “worl”,另外一个goroutine读出”d”。</p>
<h3>四、Socket属性</h3>
<p>原生Socket API提供了丰富的sockopt设置接口，但Golang有自己的网络架构模型，golang提供的socket options接口也是基于上述模型的必要的属性设置。包括</p>
<ul>
<li>SetKeepAlive</li>
<li>SetKeepAlivePeriod</li>
<li>SetLinger</li>
<li>SetNoDelay （默认no delay）</li>
<li>SetWriteBuffer</li>
<li>SetReadBuffer</li>
</ul>
<p>不过上面的Method是TCPConn的，而不是Conn的，要使用上面的Method的，需要type assertion：</p>
<pre><code>tcpConn, ok := c.(*TCPConn)
if !ok {
    //error handle
}

tcpConn.SetNoDelay(true)
</code></pre>
<p>对于listener socket, golang默认采用了 SO_REUSEADDR，这样当你重启 listener程序时，不会因为address in use的错误而启动失败。而listen backlog的默认值是通过获取系统的设置值得到的。不同系统不同：mac 128, linux 512等。</p>
<h3>五、关闭连接</h3>
<p>和前面的方法相比，关闭连接算是最简单的操作了。由于socket是全双工的，client和server端在己方已关闭的socket和对方关闭的socket上操作的结果有不同。看下面例子：</p>
<pre><code>//go-tcpsock/conn_close/client1.go
... ...
func main() {
    log.Println("begin dial...")
    conn, err := net.Dial("tcp", ":8888")
    if err != nil {
        log.Println("dial error:", err)
        return
    }
    conn.Close()
    log.Println("close ok")

    var buf = make([]byte, 32)
    n, err := conn.Read(buf)
    if err != nil {
        log.Println("read error:", err)
    } else {
        log.Printf("read % bytes, content is %s\n", n, string(buf[:n]))
    }

    n, err = conn.Write(buf)
    if err != nil {
        log.Println("write error:", err)
    } else {
        log.Printf("write % bytes, content is %s\n", n, string(buf[:n]))
    }

    time.Sleep(time.Second * 1000)
}

//go-tcpsock/conn_close/server1.go
... ...
func handleConn(c net.Conn) {
    defer c.Close()

    // read from the connection
    var buf = make([]byte, 10)
    log.Println("start to read from conn")
    n, err := c.Read(buf)
    if err != nil {
        log.Println("conn read error:", err)
    } else {
        log.Printf("read %d bytes, content is %s\n", n, string(buf[:n]))
    }

    n, err = c.Write(buf)
    if err != nil {
        log.Println("conn write error:", err)
    } else {
        log.Printf("write %d bytes, content is %s\n", n, string(buf[:n]))
    }
}
... ...

</code></pre>
<p>上述例子的执行结果如下：</p>
<pre><code>$go run server1.go
2015/11/17 17:00:51 accept a new connection
2015/11/17 17:00:51 start to read from conn
2015/11/17 17:00:51 conn read error: EOF
2015/11/17 17:00:51 write 10 bytes, content is

$go run client1.go
2015/11/17 17:00:51 begin dial...
2015/11/17 17:00:51 close ok
2015/11/17 17:00:51 read error: read tcp 127.0.0.1:64195-&gt;127.0.0.1:8888: use of closed network connection
2015/11/17 17:00:51 write error: write tcp 127.0.0.1:64195-&gt;127.0.0.1:8888: use of closed network connection
</code></pre>
<p>从client1的结果来看，在己方已经关闭的socket上再进行read和write操作，会得到”use of closed network connection” error；<br />
从server1的执行结果来看，在对方关闭的socket上执行read操作会得到EOF error，但write操作会成功，因为数据会成功写入己方的内核socket缓冲区中，即便最终发不到对方socket缓冲区了，因为己方socket并未关闭。因此当发现对方socket关闭后，己方应该正确合理处理自己的socket，再继续write已经无任何意义了。</p>
<h3>六、小结</h3>
<p>本文比较基础，但却很重要，毕竟golang是面向大规模服务后端的，对通信环节的细节的深入理解会大有裨益。另外Go的goroutine+阻塞通信的网络通信模型降低了开发者心智负担，简化了通信的复杂性，这点尤为重要。</p>
<p>本文代码实验环境：go 1.5.1 on Darwin amd64以及部分在ubuntu 14.04 amd64。</p>
<p>本文demo代码在<a href="https://github.com/bigwhite/experiments/tree/master/go-tcpsock">这里</a>可以找到。</p>
<p style='text-align:left'>&copy; 2015, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2015/11/17/tcp-programming-in-golang/feed/</wfw:commentRss>
		<slash:comments>44</slash:comments>
		</item>
		<item>
		<title>Go程序调试、分析与优化</title>
		<link>https://tonybai.com/2015/08/25/go-debugging-profiling-optimization/</link>
		<comments>https://tonybai.com/2015/08/25/go-debugging-profiling-optimization/#comments</comments>
		<pubDate>Tue, 25 Aug 2015 07:53:25 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[atomic]]></category>
		<category><![CDATA[BradFitzpatrick]]></category>
		<category><![CDATA[Channel]]></category>
		<category><![CDATA[Concurrency]]></category>
		<category><![CDATA[Darwin]]></category>
		<category><![CDATA[debugging]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[goroutine]]></category>
		<category><![CDATA[gotools]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Mutex]]></category>
		<category><![CDATA[optimization]]></category>
		<category><![CDATA[parallelism]]></category>
		<category><![CDATA[Perl]]></category>
		<category><![CDATA[pprof]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<category><![CDATA[YAPC]]></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=1804</guid>
		<description><![CDATA[Brad Fitzpatrick在YAPC Asia 2015（Yet Another Perl Conference）上做了一次技术分享，题为：&#34;Go Debugging, Profiling, and Optimization&#34;。个人感觉这篇分享中价值最大的是BradFitz现场演示的一个有关如何对Go程序进行调试、分析和优化的 Demo，Brad将demo上传到了他个人在github.com的repo中，但不知为何，repo中的代码似乎与repo里talk.md中的说明不甚一致(btw，我并没有看video)。于 是打算在这里按照Brad的思路重新走一遍demo的演示流程(所有演示代码在这里可以下载到)。 一、实验环境 $uname -a Linux pc-tony 3.13.0-61-generic #100~precise1-Ubuntu SMP Wed Jul 29 12:06:40 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux 注意:在Darwin或Windows下，profile的结果可能与这里有很大不同(甚至完全不一样的输出和瓶颈热点)。 $go version go version go1.5 linux/amd64 $ go env GOARCH=&#34;amd64&#34; GOBIN=&#34;/home1/tonybai/.bin/go15/bin&#34; GOEXE=&#34;&#34; GOHOSTARCH=&#34;amd64&#34; GOHOSTOS=&#34;linux&#34; GOOS=&#34;linux&#34; GOPATH=&#34;/home1/tonybai/proj/GoProjects&#34; GORACE=&#34;&#34; GOROOT=&#34;/home1/tonybai/.bin/go15&#34; GOTOOLDIR=&#34;/home1/tonybai/.bin/go15/pkg/tool/linux_amd64&#34; GO15VENDOREXPERIMENT=&#34;1&#34; CC=&#34;gcc&#34; GOGCCFLAGS=&#34;-fPIC -m64 [...]]]></description>
			<content:encoded><![CDATA[<p><a href="https://github.com/bradfitz">Brad Fitzpatrick</a>在<a href="http://yapcasia.org">YAPC Asia 2015</a>（Yet Another Perl Conference）上做了一次技术分享，题为：&quot;<a href="http://pan.baidu.com/s/1nt8D5oP,">Go Debugging, Profiling, and Optimization</a>&quot;。个人感觉这篇分享中价值最大的是BradFitz现场演示的一个有关如何对<a href="http://tonybai.com/tag/go">Go</a>程序进行调试、分析和优化的 Demo，Brad将demo上传到了他个人在github.com的<a href="https://github.com/bradfitz/talk- yapc-asia-2015">repo</a>中，但不知为何，repo中的代码似乎与repo里talk.md中的说明不甚一致(btw，我并没有看video)。于 是打算在这里按照Brad的思路重新走一遍demo的演示流程(所有演示代码在<a href="https://github.com/bigwhite/experiments/tree/master/go-debug-profile-optimization">这里</a>可以下载到)。</p>
<p><b>一、实验环境</b></p>
<p><font face="Courier New">$uname -a<br />
	Linux pc-tony 3.13.0-61-generic #100~precise1-Ubuntu SMP Wed Jul 29 12:06:40 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux</font></p>
<p><font face="Courier New"><strong>注意:</strong>在Darwin或Windows下，profile的结果可能与这里有很大不同(甚至完全不一样的输出和瓶颈热点)。</font></p>
<p><font face="Courier New">$go version<br />
	go version go1.5 linux/amd64</font></p>
<p><font face="Courier New">$ go env<br />
	GOARCH=&quot;amd64&quot;<br />
	GOBIN=&quot;/home1/tonybai/.bin/go15/bin&quot;<br />
	GOEXE=&quot;&quot;<br />
	GOHOSTARCH=&quot;amd64&quot;<br />
	GOHOSTOS=&quot;linux&quot;<br />
	GOOS=&quot;linux&quot;<br />
	GOPATH=&quot;/home1/tonybai/proj/GoProjects&quot;<br />
	GORACE=&quot;&quot;<br />
	GOROOT=&quot;/home1/tonybai/.bin/go15&quot;<br />
	GOTOOLDIR=&quot;/home1/tonybai/.bin/go15/pkg/tool/linux_amd64&quot;<br />
	GO15VENDOREXPERIMENT=&quot;1&quot;<br />
	CC=&quot;gcc&quot;<br />
	GOGCCFLAGS=&quot;-fPIC -m64 -pthread -fmessage-length=0&quot;<br />
	CXX=&quot;g++&quot;<br />
	CGO_ENABLED=&quot;1&quot;</font></p>
<p>代码基于Brad的<font face="Courier New">github.com/bradfitz/talk-yapc-asia-2015</font>。</p>
<p><b>二、待优化程序(step0)</b></p>
<p>待优化程序，也就是原始程序，我们放在step0中：</p>
<p><font face="Courier New">//go-debug-profile-optimization/step0/demo.go</font></p>
<p><font face="Courier New">package main</font></p>
<p><font face="Courier New">import (<br />
	&nbsp;&nbsp;&nbsp; &quot;fmt&quot;<br />
	&nbsp;&nbsp;&nbsp; &quot;log&quot;<br />
	&nbsp;&nbsp;&nbsp; &quot;net/http&quot;<br />
	&nbsp;&nbsp;&nbsp; &quot;regexp&quot;<br />
	)</font></p>
<p><font face="Courier New">var visitors int</font></p>
<p><font face="Courier New">func handleHi(w http.ResponseWriter, r *http.Request) {<br />
	&nbsp;&nbsp;&nbsp; if match, _ := regexp.MatchString(`^\w*$`, r.FormValue(&quot;color&quot;)); !match {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; http.Error(w, &quot;Optional color is invalid&quot;, http.StatusBadRequest)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return<br />
	&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; visitors++<br />
	&nbsp;&nbsp;&nbsp; w.Header().Set(&quot;Content-Type&quot;, &quot;text/html; charset=utf-8&quot;)<br />
	&nbsp;&nbsp;&nbsp; w.Write([]byte(&quot;&lt;h1 style=&#39;color: &quot; + r.FormValue(&quot;color&quot;) +<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;&#39;&gt;Welcome!&lt;/h1&gt;You are visitor number &quot; + fmt.Sprint(visitors) + &quot;!&quot;))<br />
	}</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp; log.Printf(&quot;Starting on port 8080&quot;)<br />
	&nbsp;&nbsp;&nbsp; http.HandleFunc(&quot;/hi&quot;, handleHi)<br />
	&nbsp;&nbsp;&nbsp; log.Fatal(http.ListenAndServe(&quot;127.0.0.1:8080&quot;, nil))<br />
	}</font></p>
<p><font face="Courier New">$go run demo.go<br />
	2015/08/25 09:42:35 Starting on port 8080</font></p>
<p>在浏览器输入：<font face="Courier New">http://localhost:8080/hi</font></p>
<p>一切顺利的话，页面会显示：</p>
<p><font face="Courier New">Welcome!</font></p>
<p><font face="Courier New">You are visitor number 1!</font></p>
<p><b>三、添加测试代码</b></p>
<p>按照talk.md中的说明，brad repo中demo中根本没有测试代码(<font face="Courier New">commit 2427d0faa12ed1fb05f1e6a1e69307c11259c2b2</font>)。</p>
<p>于是我根据作者的意图，新增了demo_test.go，采用TestHandleHi_Recorder和TestHandleHi_TestServer对HandleHi进行测试：</p>
<p>//<font face="Courier New">go-debug-profile-optimization/step0/demo_test.go</font><br />
	<font face="Courier New">package main</font></p>
<p><font face="Courier New">import (<br />
	&nbsp;&nbsp;&nbsp; &quot;bufio&quot;<br />
	&nbsp;&nbsp;&nbsp; &quot;net/http&quot;<br />
	&nbsp;&nbsp;&nbsp; &quot;net/http/httptest&quot;<br />
	&nbsp;&nbsp;&nbsp; &quot;strings&quot;<br />
	&nbsp;&nbsp;&nbsp; &quot;testing&quot;<br />
	)</font></p>
<p><font face="Courier New">func TestHandleHi_Recorder(t *testing.T) {<br />
	&nbsp;&nbsp;&nbsp; rw := httptest.NewRecorder()<br />
	&nbsp;&nbsp;&nbsp; handleHi(rw, req(t, &quot;GET / HTTP/1.0\r\n\r\n&quot;))<br />
	&nbsp;&nbsp;&nbsp; if !strings.Contains(rw.Body.String(), &quot;visitor number&quot;) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; t.Errorf(&quot;Unexpected output: %s&quot;, rw.Body)<br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p><font face="Courier New">func req(t *testing.T, v string) *http.Request {<br />
	&nbsp;&nbsp;&nbsp; req, err := http.ReadRequest(bufio.NewReader(strings.NewReader(v)))<br />
	&nbsp;&nbsp;&nbsp; if err != nil {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; t.Fatal(err)<br />
	&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; return req<br />
	}</font></p>
<p><font face="Courier New">func TestHandleHi_TestServer(t *testing.T) {<br />
	&nbsp;&nbsp;&nbsp; ts := httptest.NewServer(http.HandlerFunc(handleHi))<br />
	&nbsp;&nbsp;&nbsp; defer ts.Close()<br />
	&nbsp;&nbsp;&nbsp; res, err := http.Get(ts.URL)<br />
	&nbsp;&nbsp;&nbsp; if err != nil {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; t.Error(err)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return<br />
	&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; if g, w := res.Header.Get(&quot;Content-Type&quot;), &quot;text/html; charset=utf-8&quot;; g != w {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; t.Errorf(&quot;Content-Type = %q; want %q&quot;, g, w)<br />
	&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; slurp, err := ioutil.ReadAll(res.Body)<br />
	&nbsp;&nbsp;&nbsp; defer res.Body.Close()<br />
	&nbsp;&nbsp;&nbsp; if err != nil {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; t.Error(err)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return<br />
	&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; t.Logf(&quot;Got: %s&quot;, slurp)<br />
	}</font></p>
<p><font face="Courier New">$ go test -v<br />
	=== RUN&nbsp;&nbsp; TestHandleHi_Recorder<br />
	&#8212; PASS: TestHandleHi_Recorder (0.00s)<br />
	=== RUN&nbsp;&nbsp; TestHandleHi_TestServer<br />
	&#8212; PASS: TestHandleHi_TestServer (0.00s)<br />
	&nbsp;&nbsp;&nbsp; demo_test.go:45: Got: &lt;h1 style=&#39;color: &#39;&gt;Welcome!&lt;/h1&gt;You are visitor number 2!<br />
	PASS<br />
	ok&nbsp; &nbsp;&nbsp;&nbsp; _/home1/tonybai/proj/opensource/github/experiments/go-debug-profile-optimization/step0&nbsp;&nbsp;&nbsp; 0.007s</font></p>
<p>测试通过！</p>
<p>至此，step0使命结束。</p>
<p><b>四、Race Detector</b><b>(竞态分析）</b></p>
<p>并发设计使得程序可以更好更有效的利用现代处理器的多核心。但并发设计很容易引入竞态，导致严重bug。Go程序中竞态就是当多个goroutine并发 访问某共享数据且未使用同步机制时，且至少一个goroutine进行了写操作。不过go工具自带race分析功能。在分析优化step0中demo代码 前，我们先要保证demo代码中不存在竞态。</p>
<p>工具的使用方法就是在go test后加上-race标志，在step0目录下：</p>
<p><font face="Courier New">$ go test -v -race<br />
	=== RUN&nbsp;&nbsp; TestHandleHi_Recorder<br />
	&#8212; PASS: TestHandleHi_Recorder (0.00s)<br />
	=== RUN&nbsp;&nbsp; TestHandleHi_TestServer<br />
	&#8212; PASS: TestHandleHi_TestServer (0.00s)<br />
	&nbsp;&nbsp;&nbsp; demo_test.go:45: Got: &lt;h1 style=&#39;color: &#39;&gt;Welcome!&lt;/h1&gt;You are visitor number 2!<br />
	PASS<br />
	ok&nbsp; &nbsp;&nbsp;&nbsp; _/home1/tonybai/proj/opensource/github/experiments/go-debug-profile-optimization/step0&nbsp;&nbsp;&nbsp; 1.012s</font></p>
<p>-race通过做运行时分析做竞态分析，虽然不存在误报，但却存在实际有竞态，但工具没发现的情况。接下来我们改造一下测试代码，让test并发起来：</p>
<p>向step1(copy自step0)中demo_test.go中添加一个test method:</p>
<p>//<font face="Courier New">go-debug-profile-optimization/step1/demo_test.go<br />
	&#8230; &#8230;</font><br />
	<font face="Courier New">func TestHandleHi_TestServer_Parallel(t *testing.T) {<br />
	&nbsp;&nbsp;&nbsp; ts := httptest.NewServer(http.HandlerFunc(handleHi))<br />
	&nbsp;&nbsp;&nbsp; defer ts.Close()<br />
	&nbsp;&nbsp;&nbsp; var wg sync.WaitGroup<br />
	&nbsp;&nbsp;&nbsp; for i := 0; i &lt; 2; i++ {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; wg.Add(1)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; go func() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; defer wg.Done()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; res, err := http.Get(ts.URL)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if err != nil {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; t.Error(err)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if g, w := res.Header.Get(&quot;Content-Type&quot;), &quot;text/html; charset=utf-8&quot;; g != w {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; t.Errorf(&quot;Content-Type = %q; want %q&quot;, g, w)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; slurp, err := ioutil.ReadAll(res.Body)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; defer res.Body.Close()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if err != nil {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; t.Error(err)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; t.Logf(&quot;Got: %s&quot;, slurp)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }()<br />
	&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; wg.Wait()<br />
	}<br />
	&#8230; &#8230;</font></p>
<p>执行竞态test：</p>
<p><font face="Courier New">$ go test -v -race<br />
	=== RUN&nbsp;&nbsp; TestHandleHi_Recorder<br />
	&#8212; PASS: TestHandleHi_Recorder (0.00s)<br />
	=== RUN&nbsp;&nbsp; TestHandleHi_TestServer<br />
	&#8212; PASS: TestHandleHi_TestServer (0.00s)<br />
	&nbsp;&nbsp;&nbsp; demo_test.go:46: Got: &lt;h1 style=&#39;color: &#39;&gt;Welcome!&lt;/h1&gt;You are visitor number 2!<br />
	=== RUN&nbsp;&nbsp; TestHandleHi_TestServer_Parallel<br />
	==================<br />
	WARNING: DATA RACE<br />
	Read by goroutine 22:<br />
	&nbsp; _/home1/tonybai/proj/opensource/github/experiments/go-debug-profile-optimization/step1.handleHi()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /home1/tonybai/proj/opensource/github/experiments/go-debug-profile-optimization/step1/demo.go:17 +0xf5<br />
	&nbsp; net/http.HandlerFunc.ServeHTTP()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /tmp/workdir/go/src/net/http/server.go:1422 +0&#215;47<br />
	&nbsp; net/http/httptest.(*waitGroupHandler).ServeHTTP()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /tmp/workdir/go/src/net/http/httptest/server.go:200 +0xfe<br />
	&nbsp; net/http.serverHandler.ServeHTTP()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /tmp/workdir/go/src/net/http/server.go:1862 +0&#215;206<br />
	&nbsp; net/http.(*conn).serve()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /tmp/workdir/go/src/net/http/server.go:1361 +0x117c</font></p>
<p><font face="Courier New">Previous write by goroutine 25:<br />
	&nbsp; _/home1/tonybai/proj/opensource/github/experiments/go-debug-profile-optimization/step1.handleHi()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /home1/tonybai/proj/opensource/github/experiments/go-debug-profile-optimization/step1/demo.go:17 +0&#215;111<br />
	&nbsp; net/http.HandlerFunc.ServeHTTP()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /tmp/workdir/go/src/net/http/server.go:1422 +0&#215;47<br />
	&nbsp; net/http/httptest.(*waitGroupHandler).ServeHTTP()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /tmp/workdir/go/src/net/http/httptest/server.go:200 +0xfe<br />
	&nbsp; net/http.serverHandler.ServeHTTP()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /tmp/workdir/go/src/net/http/server.go:1862 +0&#215;206<br />
	&nbsp; net/http.(*conn).serve()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /tmp/workdir/go/src/net/http/server.go:1361 +0x117c</font></p>
<p><font face="Courier New">Goroutine 22 (running) created at:<br />
	&nbsp; net/http.(*Server).Serve()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /tmp/workdir/go/src/net/http/server.go:1910 +0&#215;464</font></p>
<p><font face="Courier New">Goroutine 25 (running) created at:<br />
	&nbsp; net/http.(*Server).Serve()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /tmp/workdir/go/src/net/http/server.go:1910 +0&#215;464<br />
	==================<br />
	&#8212; PASS: TestHandleHi_TestServer_Parallel (0.00s)<br />
	&nbsp;&nbsp;&nbsp; demo_test.go:71: Got: &lt;h1 style=&#39;color: &#39;&gt;Welcome!&lt;/h1&gt;You are visitor number 3!<br />
	&nbsp;&nbsp;&nbsp; demo_test.go:71: Got: &lt;h1 style=&#39;color: &#39;&gt;Welcome!&lt;/h1&gt;You are visitor number 4!<br />
	PASS<br />
	Found 1 data race(s)<br />
	exit status 66<br />
	FAIL&nbsp;&nbsp;&nbsp; _/home1/tonybai/proj/opensource/github/experiments/go-debug-profile-optimization/step1&nbsp;&nbsp;&nbsp; 1.023s</font></p>
<p>工具发现demo.go第17行：<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <font face="Courier New">visitors++</font><br />
	是一处潜在的竞态条件。</p>
<p>visitors被多个goroutine访问但未采用同步机制。</p>
<p>既然发现了竞态条件，我们就需要fix it。有多种fix方法可选：</p>
<p><font face="Courier New">1、使用channel<br />
	2、使用Mutex<br />
	3、使用atomic</font></p>
<p>Brad使用了atomic：</p>
<p><font face="Courier New">//go-debug-profile-optimization/step1/demo.go<br />
	&#8230; &#8230;<br />
	var visitors int64 // must be accessed atomically</font></p>
<p><font face="Courier New">func handleHi(w http.ResponseWriter, r *http.Request) {<br />
	&nbsp;&nbsp;&nbsp; if match, _ := regexp.MatchString(`^\w*$`, r.FormValue(&quot;color&quot;)); !match {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; http.Error(w, &quot;Optional color is invalid&quot;, http.StatusBadRequest)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return<br />
	&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; visitNum := atomic.AddInt64(&amp;visitors, 1)<br />
	&nbsp;&nbsp;&nbsp; w.Header().Set(&quot;Content-Type&quot;, &quot;text/html; charset=utf-8&quot;)<br />
	&nbsp;&nbsp;&nbsp; w.Write([]byte(&quot;&lt;h1 style=&#39;color: &quot; + r.FormValue(&quot;color&quot;) +<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;&#39;&gt;Welcome!&lt;/h1&gt;You are visitor number &quot; + fmt.Sprint(visitNum) + &quot;!&quot;))<br />
	}<br />
	&#8230; &#8230;</font></p>
<p>再做一次测试：</p>
<p><font face="Courier New">$ go test -v -race<br />
	=== RUN&nbsp;&nbsp; TestHandleHi_Recorder<br />
	&#8212; PASS: TestHandleHi_Recorder (0.00s)<br />
	=== RUN&nbsp;&nbsp; TestHandleHi_TestServer<br />
	&#8212; PASS: TestHandleHi_TestServer (0.00s)<br />
	&nbsp;&nbsp;&nbsp; demo_test.go:46: Got: &lt;h1 style=&#39;color: &#39;&gt;Welcome!&lt;/h1&gt;You are visitor number 2!<br />
	=== RUN&nbsp;&nbsp; TestHandleHi_TestServer_Parallel<br />
	&#8212; PASS: TestHandleHi_TestServer_Parallel (0.00s)<br />
	&nbsp;&nbsp;&nbsp; demo_test.go:71: Got: &lt;h1 style=&#39;color: &#39;&gt;Welcome!&lt;/h1&gt;You are visitor number 3!<br />
	&nbsp;&nbsp;&nbsp; demo_test.go:71: Got: &lt;h1 style=&#39;color: &#39;&gt;Welcome!&lt;/h1&gt;You are visitor number 4!<br />
	PASS<br />
	ok&nbsp; &nbsp;&nbsp;&nbsp; _/home1/tonybai/proj/opensource/github/experiments/go-debug-profile-optimization/step1&nbsp;&nbsp;&nbsp; 1.020s</font></p>
<p>竞态条件被消除了！</p>
<p>至此，step1结束了使命！</p>
<p><b>五、</b><b>CPU Profiling</b></p>
<p>要做CPU Profilling，我们需要benchmark数据，Go test提供benchmark test功能，我们只要写对应的Benchmark测试方法即可：</p>
<p><font face="Courier New">//go-debug-profile-optimization/step2/demo_test.go<br />
	&#8230; &#8230;<br />
	func BenchmarkHi(b *testing.B) {<br />
	&nbsp;&nbsp;&nbsp; b.ReportAllocs()</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; req, err := http.ReadRequest(bufio.NewReader(strings.NewReader(&quot;GET / HTTP/1.0\r\n\r\n&quot;)))<br />
	&nbsp;&nbsp;&nbsp; if err != nil {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b.Fatal(err)<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; for i := 0; i &lt; b.N; i++ {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; rw := httptest.NewRecorder()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; handleHi(rw, req)<br />
	&nbsp;&nbsp;&nbsp; }<br />
	}<br />
	&#8230; &#8230;</font></p>
<p><font face="Courier New">$ go test -v -run=^$ -bench=.<br />
	PASS<br />
	BenchmarkHi-4&nbsp;&nbsp;&nbsp; &nbsp; 100000&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; 14808 ns/op&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 4961 B/op&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 81 allocs/op<br />
	ok&nbsp; &nbsp;&nbsp;&nbsp; _/home1/tonybai/proj/opensource/github/experiments/go-debug-profile-optimization/step2&nbsp;&nbsp;&nbsp; 1.648s</font></p>
<p>开始CPU Profiling：</p>
<p><font face="Courier New">$ go test -v -run=^$ -bench=^BenchmarkHi$ -benchtime=2s -cpuprofile=prof.cpu<br />
	PASS<br />
	BenchmarkHi-4&nbsp;&nbsp;&nbsp; &nbsp; 200000&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; 14679 ns/op&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 4961 B/op&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 81 allocs/op<br />
	ok&nbsp; &nbsp;&nbsp;&nbsp; _/home1/tonybai/proj/opensource/github/experiments/go-debug-profile-optimization/step2&nbsp;&nbsp;&nbsp; 3.096s</font></p>
<p><font face="Courier New">执行完benchmark test后，step2目录下出现两个新文件prof.cpu和step2.test，这两个文件将作为后续go tool pprof的输入：<br />
	$ls<br />
	demo.go&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; demo_test.go&nbsp;&nbsp;&nbsp; prof.cpu&nbsp;&nbsp;&nbsp; step2.test*</font></p>
<p>使用go profile viewer工具：</p>
<p><font face="Courier New">$ go tool pprof step2.test prof.cpu<br />
	Entering interactive mode (type &quot;help&quot; for commands)<br />
	(pprof) top<br />
	1830ms of 3560ms total (51.40%)<br />
	Dropped 53 nodes (cum &lt;= 17.80ms)<br />
	Showing top 10 nodes out of 133 (cum &gt;= 1290ms)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; flat&nbsp; flat%&nbsp;&nbsp; sum%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cum&nbsp;&nbsp; cum%<br />
	&nbsp;&nbsp;&nbsp;&nbsp; 480ms 13.48% 13.48%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 980ms 27.53%&nbsp; runtime.growslice<br />
	&nbsp;&nbsp;&nbsp;&nbsp; 360ms 10.11% 23.60%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 700ms 19.66%&nbsp; runtime.mallocgc<br />
	&nbsp;&nbsp;&nbsp;&nbsp; 170ms&nbsp; 4.78% 28.37%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 170ms&nbsp; 4.78%&nbsp; runtime.heapBitsSetType<br />
	&nbsp;&nbsp;&nbsp;&nbsp; 170ms&nbsp; 4.78% 33.15%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 200ms&nbsp; 5.62%&nbsp; runtime.scanblock<br />
	&nbsp;&nbsp;&nbsp;&nbsp; 120ms&nbsp; 3.37% 36.52%&nbsp;&nbsp;&nbsp;&nbsp; 1100ms 30.90%&nbsp; regexp.makeOnePass.func2<br />
	&nbsp;&nbsp;&nbsp;&nbsp; 120ms&nbsp; 3.37% 39.89%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 550ms 15.45%&nbsp; runtime.newarray<br />
	&nbsp;&nbsp;&nbsp;&nbsp; 110ms&nbsp; 3.09% 42.98%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 300ms&nbsp; 8.43%&nbsp; runtime.makeslice<br />
	&nbsp;&nbsp;&nbsp;&nbsp; 110ms&nbsp; 3.09% 46.07%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 220ms&nbsp; 6.18%&nbsp; runtime.mapassign1<br />
	&nbsp;&nbsp;&nbsp;&nbsp; 100ms&nbsp; 2.81% 48.88%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 100ms&nbsp; 2.81%&nbsp; runtime.futex<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 90ms&nbsp; 2.53% 51.40%&nbsp;&nbsp;&nbsp;&nbsp; 1290ms 36.24%&nbsp; regexp.makeOnePass</font></p>
<p><font face="Courier New">(pprof) top &#8211;cum<br />
	0.18s of 3.56s total ( 5.06%)<br />
	Dropped 53 nodes (cum &lt;= 0.02s)<br />
	Showing top 10 nodes out of 133 (cum &gt;= 1.29s)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; flat&nbsp; flat%&nbsp;&nbsp; sum%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cum&nbsp;&nbsp; cum%<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0%&nbsp;&nbsp;&nbsp;&nbsp; 0%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 3.26s 91.57%&nbsp; runtime.goexit<br />
	&nbsp;&nbsp;&nbsp;&nbsp; 0.02s&nbsp; 0.56%&nbsp; 0.56%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2.87s 80.62%&nbsp; BenchmarkHi<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0%&nbsp; 0.56%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2.87s 80.62%&nbsp; testing.(*B).launch<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0%&nbsp; 0.56%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2.87s 80.62%&nbsp; testing.(*B).runN<br />
	&nbsp;&nbsp;&nbsp;&nbsp; 0.03s&nbsp; 0.84%&nbsp; 1.40%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>2.80s 78.65%</b>&nbsp; <b>step2.handleHi</b><br />
	&nbsp;&nbsp;&nbsp;&nbsp; 0.01s&nbsp; 0.28%&nbsp; 1.69%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2.46s 69.10%&nbsp; regexp.MatchString<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0%&nbsp; 1.69%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2.24s 62.92%&nbsp; regexp.Compile<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0%&nbsp; 1.69%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2.24s 62.92%&nbsp; regexp.compile<br />
	&nbsp;&nbsp;&nbsp;&nbsp; 0.03s&nbsp; 0.84%&nbsp; 2.53%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1.56s 43.82%&nbsp; regexp.compileOnePass<br />
	&nbsp;&nbsp;&nbsp;&nbsp; 0.09s&nbsp; 2.53%&nbsp; 5.06%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1.29s 36.24%&nbsp; regexp.makeOnePass</font></p>
<p><font face="Courier New">(pprof) list handleHi<br />
	Total: 3.56s<br />
	ROUTINE ======================== handleHi in go-debug-profile-optimization/step2/demo.go<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 30ms&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2.80s (flat, cum) 78.65% of Total<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 9:)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 10:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 11:var visitors int64 // must be accessed atomically<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 12:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 13:func handleHi(w http.ResponseWriter, r *http.Request) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <b>2.47s</b>&nbsp;&nbsp;&nbsp;&nbsp; 14:&nbsp;&nbsp;&nbsp; if match, _ := regexp.<b>MatchString</b>(`^\w*$`, r.FormValue(&quot;color&quot;)); !match {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 15:&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; http.Error(w, &quot;Optional color is invalid&quot;, http.StatusBadRequest)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 16:&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 17:&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 10ms&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 20ms&nbsp;&nbsp;&nbsp;&nbsp; 18:&nbsp;&nbsp;&nbsp; visitNum := atomic.AddInt64(&amp;visitors, 1)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 10ms&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 90ms&nbsp;&nbsp;&nbsp;&nbsp; 19:&nbsp;&nbsp;&nbsp; w.Header().Set(&quot;Content-Type&quot;, &quot;text/html; charset=utf-8&quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 10ms&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 20ms&nbsp;&nbsp;&nbsp;&nbsp; 20:&nbsp;&nbsp;&nbsp; w.Write([]byte(&quot;&lt;h1 style=&#39;color: &quot; + r.FormValue(&quot;color&quot;) +<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 200ms&nbsp;&nbsp;&nbsp;&nbsp; 21:&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &quot;&#39;&gt;Welcome!&lt;/h1&gt;You are visitor number &quot; + fmt.Sprint(visitNum) + &quot;!&quot;))<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 22:}<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 23:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 24:func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 25:&nbsp;&nbsp;&nbsp; log.Printf(&quot;Starting on port 8080&quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 26:&nbsp;&nbsp;&nbsp; http.HandleFunc(&quot;/hi&quot;, handleHi)<br />
	(pprof)</font></p>
<p><font face="Courier New">从top &#8211;cum来看，handleHi消耗cpu较大，而handleHi中，又是MatchString耗时最长。</font></p>
<p><font face="Courier New"><b>六、第一次优化</b></font></p>
<p><font face="Courier New">前面已经发现MatchString较为耗时，优化手段：让正则式仅编译一次(step3)：</font></p>
<p><font face="Courier New">// </font><font face="Courier New"><font face="Courier New">go-debug-profile-optimization/step3/demo.go</font></font></p>
<p><font face="Courier New"><font face="Courier New">&#8230; &#8230;</font><br />
	var visitors int64 // must be accessed atomically</font></p>
<p><font face="Courier New"><b>var rxOptionalID = regexp.MustCompile(`^\d*$`)</b></font></p>
<p><font face="Courier New">func handleHi(w http.ResponseWriter, r *http.Request) {<br />
	&nbsp;&nbsp;&nbsp; if !rxOptionalID.MatchString(r.FormValue(&quot;color&quot;)) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; http.Error(w, &quot;Optional color is invalid&quot;, http.StatusBadRequest)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; visitNum := atomic.AddInt64(&amp;visitors, 1)<br />
	&nbsp;&nbsp;&nbsp; w.Header().Set(&quot;Content-Type&quot;, &quot;text/html; charset=utf-8&quot;)<br />
	&nbsp;&nbsp;&nbsp; w.Write([]byte(&quot;&lt;h1 style=&#39;color: &quot; + r.FormValue(&quot;color&quot;) +<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;&#39;&gt;Welcome!&lt;/h1&gt;You are visitor number &quot; + fmt.Sprint(visitNum) + &quot;!&quot;))<br />
	}<br />
	&#8230; &#8230;</font></p>
<p><font face="Courier New">运行一下bench：</font></p>
<p><font face="Courier New">$ go test -bench=.<br />
	PASS<br />
	BenchmarkHi-4&nbsp;&nbsp;&nbsp; &nbsp;1000000&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1678 ns/op&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; 720 B/op&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 9 allocs/op<br />
	ok&nbsp; &nbsp;&nbsp;&nbsp; _/home1/tonybai/proj/opensource/github/experiments/go-debug-profile-optimization/step3&nbsp;&nbsp;&nbsp; 1.710s</font></p>
<p><font face="Courier New">对比之前在step2中运行的bench结果：</font></p>
<p><font face="Courier New">$ go test -v -run=^$ -bench=.<br />
	PASS<br />
	BenchmarkHi-4&nbsp;&nbsp;&nbsp; &nbsp; 100000&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; 14808 ns/op&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 4961 B/op&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 81 allocs/op<br />
	ok&nbsp; &nbsp;&nbsp;&nbsp; _/home1/tonybai/proj/opensource/github/experiments/go-debug-profile-optimization/step2&nbsp;&nbsp;&nbsp; 1.648s</font></p>
<p>耗时相同，但优化后的bench运行了100w次，而之前的Bench运行10w次，相当于性能提高10倍。</p>
<p>再看看cpu prof结果：</p>
<p><font face="Courier New">$ go test -v -run=^$ -bench=^BenchmarkHi$ -benchtime=3s -cpuprofile=prof.cpu<br />
	PASS<br />
	BenchmarkHi-4&nbsp;&nbsp;&nbsp; &nbsp;3000000&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1640 ns/op&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; 720 B/op&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 9 allocs/op<br />
	ok&nbsp; &nbsp;&nbsp;&nbsp; _/home1/tonybai/proj/opensource/github/experiments/go-debug-profile-optimization/step3&nbsp;&nbsp;&nbsp; 6.540s</font></p>
<p><font face="Courier New">$ go tool pprof step3.test prof.cpu<br />
	Entering interactive mode (type &quot;help&quot; for commands)<br />
	(pprof) top &#8211;cum 30<br />
	2.74s of 8.07s total (33.95%)<br />
	Dropped 72 nodes (cum &lt;= 0.04s)<br />
	Showing top 30 nodes out of 103 (cum &gt;= 0.56s)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; flat&nbsp; flat%&nbsp;&nbsp; sum%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cum&nbsp;&nbsp; cum%<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0%&nbsp;&nbsp;&nbsp;&nbsp; 0%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 7.17s 88.85%&nbsp; runtime.goexit<br />
	&nbsp;&nbsp;&nbsp;&nbsp; 0.05s&nbsp; 0.62%&nbsp; 0.62%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 6.21s 76.95%&nbsp; step3.BenchmarkHi<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0%&nbsp; 0.62%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 6.21s 76.95%&nbsp; testing.(*B).launch<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0%&nbsp; 0.62%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 6.21s 76.95%&nbsp; testing.(*B).runN<br />
	&nbsp;&nbsp;&nbsp;&nbsp; 0.06s&nbsp; 0.74%&nbsp; 1.36%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 4.96s <b>61.46%&nbsp; step3.handleHi</b><br />
	&nbsp;&nbsp;&nbsp;&nbsp; 1.15s 14.25% 15.61%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2.35s 29.12%&nbsp; runtime.mallocgc<br />
	&nbsp;&nbsp;&nbsp;&nbsp; 0.02s&nbsp; 0.25% 15.86%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1.63s 20.20%&nbsp; runtime.systemstack<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0% 15.86%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1.53s 18.96%&nbsp; net/http.Header.Set<br />
	&nbsp;&nbsp;&nbsp;&nbsp; 0.06s&nbsp; 0.74% 16.60%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1.53s 18.96%&nbsp; net/textproto.MIMEHeader.Set<br />
	&nbsp;&nbsp;&nbsp;&nbsp; 0.09s&nbsp; 1.12% 17.72%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1.22s 15.12%&nbsp; runtime.newobject<br />
	&nbsp;&nbsp;&nbsp;&nbsp; 0.05s&nbsp; 0.62% 18.34%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1.09s 13.51%&nbsp; fmt.Sprint<br />
	&nbsp;&nbsp;&nbsp;&nbsp; 0.20s&nbsp; 2.48% 20.82%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1s 12.39%&nbsp; runtime.mapassign1<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0% 20.82%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0.81s 10.04%&nbsp; runtime.mcall<br />
	&nbsp;&nbsp;&nbsp;&nbsp; 0.01s&nbsp; 0.12% 20.94%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0.79s&nbsp; 9.79%&nbsp; runtime.schedule<br />
	&nbsp;&nbsp;&nbsp;&nbsp; 0.05s&nbsp; 0.62% 21.56%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0.76s&nbsp; 9.42%&nbsp; regexp.(*Regexp).MatchString<br />
	&nbsp;&nbsp;&nbsp;&nbsp; 0.09s&nbsp; 1.12% 22.68%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0.71s&nbsp; 8.80%&nbsp; regexp.(*Regexp).doExecute<br />
	&nbsp;&nbsp;&nbsp;&nbsp; 0.01s&nbsp; 0.12% 22.80%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0.71s&nbsp; 8.80%&nbsp; runtime.concatstring5<br />
	&nbsp;&nbsp;&nbsp;&nbsp; 0.20s&nbsp; 2.48% 25.28%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0.70s&nbsp; 8.67%&nbsp; runtime.concatstrings<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0% 25.28%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0.69s&nbsp; 8.55%&nbsp; runtime.gosweepone<br />
	&nbsp;&nbsp;&nbsp;&nbsp; 0.05s&nbsp; 0.62% 25.90%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0.69s&nbsp; 8.55%&nbsp; runtime.mSpan_Sweep<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0% 25.90%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0.68s&nbsp; 8.43%&nbsp; runtime.bgsweep<br />
	&nbsp;&nbsp;&nbsp;&nbsp; 0.04s&nbsp;&nbsp; 0.5% 26.39%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0.68s&nbsp; 8.43%&nbsp; runtime.newarray<br />
	&nbsp;&nbsp;&nbsp;&nbsp; 0.01s&nbsp; 0.12% 26.52%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0.67s&nbsp; 8.30%&nbsp; runtime.goschedImpl<br />
	&nbsp;&nbsp;&nbsp;&nbsp; 0.01s&nbsp; 0.12% 26.64%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0.65s&nbsp; 8.05%&nbsp; runtime.gosched_m<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0% 26.64%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0.65s&nbsp; 8.05%&nbsp; runtime.gosweepone.func1<br />
	&nbsp;&nbsp;&nbsp;&nbsp; 0.01s&nbsp; 0.12% 26.77%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0.65s&nbsp; 8.05%&nbsp; runtime.sweepone<br />
	&nbsp;&nbsp;&nbsp;&nbsp; 0.28s&nbsp; 3.47% 30.24%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0.62s&nbsp; 7.68%&nbsp; runtime.makemap<br />
	&nbsp;&nbsp;&nbsp;&nbsp; 0.17s&nbsp; 2.11% 32.34%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0.59s&nbsp; 7.31%&nbsp; runtime.heapBitsSweepSpan<br />
	&nbsp;&nbsp;&nbsp;&nbsp; 0.02s&nbsp; 0.25% 32.59%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0.58s&nbsp; 7.19%&nbsp; fmt.(*pp).doPrint<br />
	&nbsp;&nbsp;&nbsp;&nbsp; 0.11s&nbsp; 1.36% 33.95%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0.56s&nbsp; 6.94%&nbsp; fmt.(*pp).printArg</font></p>
<p>handleHi耗时有一定下降。</p>
<p><b>七、Mem Profiling</b></p>
<p>在step3目录下执行bench，获取mem分配数据：</p>
<p><font face="Courier New">$ go test -v -run=^$ -bench=^BenchmarkHi$ -benchtime=2s -memprofile=prof.mem<br />
	PASS<br />
	BenchmarkHi-4&nbsp;&nbsp;&nbsp; &nbsp;2000000&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1657 ns/op&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; 720 B/op&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 9 allocs/op<br />
	ok&nbsp; &nbsp;&nbsp;&nbsp; _/home1/tonybai/proj/opensource/github/experiments/go-debug-profile-optimization/step3&nbsp;&nbsp;&nbsp; 5.002s</font></p>
<p>使用pprof工具分析mem：</p>
<p><font face="Courier New">$ go tool pprof &#8211;alloc_space step3.test prof.mem<br />
	Entering interactive mode (type &quot;help&quot; for commands)<br />
	(pprof) top<br />
	2065.91MB of 2067.41MB total (99.93%)<br />
	Dropped 14 nodes (cum &lt;= 10.34MB)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; flat&nbsp; flat%&nbsp;&nbsp; sum%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cum&nbsp;&nbsp; cum%<br />
	&nbsp;1076.35MB 52.06% 52.06%&nbsp; 1076.35MB 52.06%&nbsp; net/textproto.MIMEHeader.Set<br />
	&nbsp; 535.54MB 25.90% 77.97%&nbsp; 2066.91MB&nbsp;&nbsp; 100%&nbsp; step3.BenchmarkHi<br />
	&nbsp; 406.52MB 19.66% 97.63%&nbsp; <b>1531.37MB 74.07%&nbsp; step3.handleHi</b><br />
	&nbsp;&nbsp; 47.50MB&nbsp; 2.30% 99.93%&nbsp;&nbsp;&nbsp; 48.50MB&nbsp; 2.35%&nbsp; fmt.Sprint<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0% 99.93%&nbsp; 1076.35MB 52.06%&nbsp; net/http.Header.Set<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0% 99.93%&nbsp; 2066.91MB&nbsp;&nbsp; 100%&nbsp; runtime.goexit<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0% 99.93%&nbsp; 2066.91MB&nbsp;&nbsp; 100%&nbsp; testing.(*B).launch<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0% 99.93%&nbsp; 2066.91MB&nbsp;&nbsp; 100%&nbsp; testing.(*B).runN</font></p>
<p><font face="Courier New">(pprof) top -cum<br />
	2065.91MB of 2067.41MB total (99.93%)<br />
	Dropped 14 nodes (cum &lt;= 10.34MB)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; flat&nbsp; flat%&nbsp;&nbsp; sum%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cum&nbsp;&nbsp; cum%<br />
	&nbsp; 535.54MB 25.90% 25.90%&nbsp; 2066.91MB&nbsp;&nbsp; 100%&nbsp; step3.BenchmarkHi<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0% 25.90%&nbsp; 2066.91MB&nbsp;&nbsp; 100%&nbsp; runtime.goexit<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0% 25.90%&nbsp; 2066.91MB&nbsp;&nbsp; 100%&nbsp; testing.(*B).launch<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0% 25.90%&nbsp; 2066.91MB&nbsp;&nbsp; 100%&nbsp; testing.(*B).runN<br />
	&nbsp; 406.52MB 19.66% 45.57%&nbsp; <b>1531.37MB 74.07%&nbsp; step3.handleHi</b><br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0% 45.57%&nbsp; 1076.35MB 52.06%&nbsp; net/http.Header.Set<br />
	&nbsp;1076.35MB 52.06% 97.63%&nbsp; 1076.35MB 52.06%&nbsp; net/textproto.MIMEHeader.Set<br />
	&nbsp;&nbsp; 47.50MB&nbsp; 2.30% 99.93%&nbsp;&nbsp;&nbsp; 48.50MB&nbsp; 2.35%&nbsp; fmt.Sprint</font></p>
<p><font face="Courier New">(pprof) list handleHi<br />
	Total: 2.02GB<br />
	&nbsp;&nbsp;&nbsp;&nbsp; ROUTINE =========step3.handleHi in step3/demo.go<br />
	&nbsp; 406.52MB&nbsp;&nbsp;&nbsp;&nbsp; 1.50GB (flat, cum) 74.07% of Total<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 17:&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; http.Error(w, &quot;Optional color is invalid&quot;, http.StatusBadRequest)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 18:&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 19:&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 20:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 21:&nbsp;&nbsp;&nbsp; visitNum := atomic.AddInt64(&amp;visitors, 1)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 1.05GB&nbsp;&nbsp;&nbsp;&nbsp; 22:&nbsp;&nbsp;&nbsp; w.Header().Set(&quot;Content-Type&quot;, &quot;text/html; charset=utf-8&quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 23:&nbsp;&nbsp;&nbsp; w.Write([]byte(&quot;&lt;h1 style=&#39;color: &quot; + r.FormValue(&quot;color&quot;) +<br />
	&nbsp; 406.52MB&nbsp;&nbsp; 455.02MB&nbsp;&nbsp;&nbsp;&nbsp; 24:&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &quot;&#39;&gt;Welcome!&lt;/h1&gt;You are visitor number &quot; + fmt.Sprint(visitNum) + &quot;!&quot;))<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 25:}<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 26:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 27:func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 28:&nbsp;&nbsp;&nbsp; log.Printf(&quot;Starting on port 8080&quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 29:&nbsp;&nbsp;&nbsp; http.HandleFunc(&quot;/hi&quot;, handleHi)<br />
	(pprof)</font></p>
<p>可以看到handleHi22、23两行占用了较多内存。</p>
<p><b>八、第二次优化</b></p>
<p>第二次优化的方法：<br />
	1、删除<font face="Courier New">w.Header().Set这行<br />
	2、用fmt.Fprintf替代w.Write</font></p>
<p><font face="Courier New">第二次优化的代码在step4目录中：</font></p>
<p><font face="Courier New">// go-debug-profile-optimization/step4/demo.go<br />
	&#8230; &#8230;<br />
	func handleHi(w http.ResponseWriter, r *http.Request) {<br />
	&nbsp;&nbsp;&nbsp; if !rxOptionalID.MatchString(r.FormValue(&quot;color&quot;)) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; http.Error(w, &quot;Optional color is invalid&quot;, http.StatusBadRequest)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; visitNum := atomic.AddInt64(&amp;visitors, 1)<br />
	&nbsp;&nbsp;&nbsp; <b>fmt.Fprintf(w, &quot;&lt;html&gt;&lt;h1 stype=&#39;color: \&quot;%s\&quot;&#39;&gt;Welcome!&lt;/h1&gt;You are visitor number %d!&quot;, r.FormValue(&quot;color&quot;), visitNum)</b><br />
	}<br />
	&#8230; &#8230;</font></p>
<p><font face="Courier New">执行一遍pprof:</font></p>
<p><font face="Courier New">$ go test -v -run=^$ -bench=^BenchmarkHi$ -benchtime=2s -memprofile=prof.mem<br />
	PASS<br />
	BenchmarkHi-4&nbsp;&nbsp;&nbsp; &nbsp;2000000&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1428 ns/op&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; 304 B/op&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 6 allocs/op<br />
	ok&nbsp; &nbsp;&nbsp;&nbsp; _/home1/tonybai/proj/opensource/github/experiments/go-debug-profile-optimization/step4&nbsp;&nbsp;&nbsp; 4.343s</font></p>
<p><font face="Courier New">$ go tool pprof &#8211;alloc_space step4.test prof.mem<br />
	Entering interactive mode (type &quot;help&quot; for commands)<br />
	(pprof) top<br />
	868.06MB of 868.56MB total (99.94%)<br />
	Dropped 5 nodes (cum &lt;= 4.34MB)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; flat&nbsp; flat%&nbsp;&nbsp; sum%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cum&nbsp;&nbsp; cum%<br />
	&nbsp; 559.54MB 64.42% 64.42%&nbsp;&nbsp; 868.06MB 99.94%&nbsp; step4.BenchmarkHi<br />
	&nbsp; 219.52MB 25.27% 89.70%&nbsp;&nbsp; 219.52MB 25.27%&nbsp; bytes.makeSlice<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 89MB 10.25% 99.94%&nbsp;&nbsp; 308.52MB 35.52%&nbsp; step4.handleHi<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0% 99.94%&nbsp;&nbsp; 219.52MB 25.27%&nbsp; bytes.(*Buffer).Write<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0% 99.94%&nbsp;&nbsp; 219.52MB 25.27%&nbsp; bytes.(*Buffer).grow<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0% 99.94%&nbsp;&nbsp; 219.52MB 25.27%&nbsp; fmt.Fprintf<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0% 99.94%&nbsp;&nbsp; 219.52MB 25.27%&nbsp; net/http/httptest.(*ResponseRecorder).Write<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0% 99.94%&nbsp;&nbsp; 868.06MB 99.94%&nbsp; runtime.goexit<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0% 99.94%&nbsp;&nbsp; 868.06MB 99.94%&nbsp; testing.(*B).launch<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0% 99.94%&nbsp;&nbsp; 868.06MB 99.94%&nbsp; testing.(*B).runN<br />
	(pprof) top &#8211;cum<br />
	868.06MB of 868.56MB total (99.94%)<br />
	Dropped 5 nodes (cum &lt;= 4.34MB)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; flat&nbsp; flat%&nbsp;&nbsp; sum%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cum&nbsp;&nbsp; cum%<br />
	&nbsp; 559.54MB 64.42% 64.42%&nbsp;&nbsp; 868.06MB 99.94%&nbsp; step4.BenchmarkHi<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0% 64.42%&nbsp;&nbsp; 868.06MB 99.94%&nbsp; runtime.goexit<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0% 64.42%&nbsp;&nbsp; 868.06MB 99.94%&nbsp; testing.(*B).launch<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0% 64.42%&nbsp;&nbsp; 868.06MB 99.94%&nbsp; testing.(*B).runN<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 89MB 10.25% 74.67%&nbsp;&nbsp; <b>308.52MB </b>35.52%&nbsp; step4.handleHi<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0% 74.67%&nbsp;&nbsp; 219.52MB 25.27%&nbsp; bytes.(*Buffer).Write<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0% 74.67%&nbsp;&nbsp; 219.52MB 25.27%&nbsp; bytes.(*Buffer).grow<br />
	&nbsp; 219.52MB 25.27% 99.94%&nbsp;&nbsp; 219.52MB 25.27%&nbsp; bytes.makeSlice<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0% 99.94%&nbsp;&nbsp; 219.52MB 25.27%&nbsp; fmt.Fprintf<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0% 99.94%&nbsp;&nbsp; 219.52MB 25.27%&nbsp; net/http/httptest.(*ResponseRecorder).Write<br />
	(pprof) list handleHi<br />
	Total: 868.56MB<br />
	ROUTINE ============ step4.handleHi in step4/demo.go<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 89MB&nbsp;&nbsp; 308.52MB (flat, cum) 35.52% of Total<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 17:&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; http.Error(w, &quot;Optional color is invalid&quot;, http.StatusBadRequest)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 18:&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 19:&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 20:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 21:&nbsp;&nbsp;&nbsp; visitNum := atomic.AddInt64(&amp;visitors, 1)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 89MB&nbsp;&nbsp; <b>308.52MB</b>&nbsp;&nbsp;&nbsp;&nbsp; 22:&nbsp;&nbsp;&nbsp; fmt.Fprintf(w, &quot;&lt;html&gt;&lt;h1 stype=&#39;color: \&quot;%s\&quot;&#39;&gt;Welcome!&lt;/h1&gt;You are visitor number %d!&quot;, r.FormValue(&quot;color&quot;), visitNum)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 23:}<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 24:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 25:func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 26:&nbsp;&nbsp;&nbsp; log.Printf(&quot;Starting on port 8080&quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 27:&nbsp;&nbsp;&nbsp; http.HandleFunc(&quot;/hi&quot;, handleHi)<br />
	(pprof)</font></p>
<p>可以看出内存占用大幅减少。</p>
<p><b>九、Benchcmp</b></p>
<p>golang.org/x/tools中有一个工具：benchcmp，可以给出两次bench的结果对比。</p>
<p>github.com/golang/tools是golang.org/x/tools的一个镜像。安装benchcmp步骤：</p>
<p><font face="Courier New">1、go get -u github.com/golang/tools<br />
	2、mkdir -p $GOPATH/src/golang.org/x<br />
	3、mv $GOPATH/src/github.com/golang/tools $GOPATH/src/golang.org/x<br />
	4、go install golang.org/x/tools/cmd/benchcmp</font></p>
<p>我们分别在step2、step3和step4下执行如下命令：</p>
<p><font face="Courier New">$ go-debug-profile-optimization/step2$ go test -bench=. -memprofile=prof.mem | tee mem.2<br />
	PASS<br />
	BenchmarkHi-4&nbsp;&nbsp;&nbsp; &nbsp; 100000&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; 14786 ns/op&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 4961 B/op&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 81 allocs/op<br />
	ok&nbsp; &nbsp;&nbsp;&nbsp; _/home1/tonybai/proj/opensource/github/experiments/go-debug-profile-optimization/step2&nbsp;&nbsp;&nbsp; 1.644s</font></p>
<p><font face="Courier New">go-debug-profile-optimization/step3$ go test -bench=. -memprofile=prof.mem | tee mem.3<br />
	PASS<br />
	BenchmarkHi-4&nbsp;&nbsp;&nbsp; &nbsp;1000000&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1662 ns/op&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; 720 B/op&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 9 allocs/op<br />
	ok&nbsp; &nbsp;&nbsp;&nbsp; _/home1/tonybai/proj/opensource/github/experiments/go-debug-profile-optimization/step3&nbsp;&nbsp;&nbsp; 1.694s</font></p>
<p><font face="Courier New">go-debug-profile-optimization/step4$ go test -bench=. -memprofile=prof.mem | tee mem.4<br />
	PASS<br />
	BenchmarkHi-4&nbsp;&nbsp;&nbsp; &nbsp;1000000&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1428 ns/op&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; 304 B/op&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 6 allocs/op<br />
	ok&nbsp; &nbsp;&nbsp;&nbsp; _/home1/tonybai/proj/opensource/github/experiments/go-debug-profile-optimization/step4&nbsp;&nbsp;&nbsp; 1.456s</font></p>
<p>利用benchcmp工具对比结果（benchcmp old new）：</p>
<p><font face="Courier New">$ benchcmp step3/mem.3 step4/mem.4<br />
	benchmark&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; old ns/op&nbsp;&nbsp;&nbsp;&nbsp; new ns/op&nbsp;&nbsp;&nbsp;&nbsp; delta<br />
	BenchmarkHi-4&nbsp;&nbsp;&nbsp;&nbsp; 1662&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1428&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -14.08%</font></p>
<p><font face="Courier New">benchmark&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; old allocs&nbsp;&nbsp;&nbsp;&nbsp; new allocs&nbsp;&nbsp;&nbsp;&nbsp; delta<br />
	BenchmarkHi-4&nbsp;&nbsp;&nbsp;&nbsp; 9&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 6&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -33.33%</font></p>
<p><font face="Courier New">benchmark&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; old bytes&nbsp;&nbsp;&nbsp;&nbsp; new bytes&nbsp;&nbsp;&nbsp;&nbsp; delta<br />
	BenchmarkHi-4&nbsp;&nbsp;&nbsp;&nbsp; 720&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 304&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -57.78%</font></p>
<p><font face="Courier New">$ benchcmp step2/mem.2 step4/mem.4<br />
	benchmark&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; old ns/op&nbsp;&nbsp;&nbsp;&nbsp; new ns/op&nbsp;&nbsp;&nbsp;&nbsp; delta<br />
	BenchmarkHi-4&nbsp;&nbsp;&nbsp;&nbsp; 14786&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1428&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -90.34%</font></p>
<p><font face="Courier New">benchmark&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; old allocs&nbsp;&nbsp;&nbsp;&nbsp; new allocs&nbsp;&nbsp;&nbsp;&nbsp; delta<br />
	BenchmarkHi-4&nbsp;&nbsp;&nbsp;&nbsp; 81&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 6&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -92.59%</font></p>
<p><font face="Courier New">benchmark&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; old bytes&nbsp;&nbsp;&nbsp;&nbsp; new bytes&nbsp;&nbsp;&nbsp;&nbsp; delta<br />
	BenchmarkHi-4&nbsp;&nbsp;&nbsp;&nbsp; 4961&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 304&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -93.87%</font></p>
<p>可以看出优化后，内存分配大幅减少，gc的时间也随之减少。</p>
<p><b>十、内存来自哪</b></p>
<p>我们在BenchmarkHi中清理每次handleHi执行后的内存：</p>
<p><font face="Courier New">//step5/demo_test.go</font><br />
	<font face="Courier New">&#8230; &#8230;<br />
	func BenchmarkHi(b *testing.B) {<br />
	&nbsp;&nbsp;&nbsp; b.ReportAllocs()</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; req, err := http.ReadRequest(bufio.NewReader(strings.NewReader(&quot;GET / HTTP/1.0\r\n\r\n&quot;)))<br />
	&nbsp;&nbsp;&nbsp; if err != nil {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b.Fatal(err)<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; for i := 0; i &lt; b.N; i++ {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; rw := httptest.NewRecorder()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; handleHi(rw, req)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; reset(rw)<br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p><font face="Courier New">func reset(rw *httptest.ResponseRecorder) {<br />
	&nbsp;&nbsp;&nbsp; m := rw.HeaderMap<br />
	&nbsp;&nbsp;&nbsp; for k := range m {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; delete(m, k)<br />
	&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; body := rw.Body<br />
	&nbsp;&nbsp;&nbsp; body.Reset()<br />
	&nbsp;&nbsp;&nbsp; *rw = httptest.ResponseRecorder{<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Body:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; body,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; HeaderMap: m,<br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p><font face="Courier New">&#8230; &#8230;</font><br />
	<font face="Courier New">$ go test -v -run=^$ -bench=^BenchmarkHi$ -benchtime=2s -memprofile=prof.mem<br />
	PASS<br />
	BenchmarkHi-4&nbsp;&nbsp;&nbsp; &nbsp;2000000&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1518 ns/op&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; 304 B/op&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 6 allocs/op<br />
	ok&nbsp; &nbsp;&nbsp;&nbsp; _/home1/tonybai/proj/opensource/github/experiments/go-debug-profile-optimization/step5&nbsp;&nbsp;&nbsp; 4.577s</font></p>
<p><font face="Courier New">$ go tool pprof &#8211;alloc_space step5.test prof.mem<br />
	Entering interactive mode (type &quot;help&quot; for commands)<br />
	(pprof) top &#8211;cum 10<br />
	290.52MB of 291.52MB total (99.66%)<br />
	Dropped 14 nodes (cum &lt;= 1.46MB)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; flat&nbsp; flat%&nbsp;&nbsp; sum%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cum&nbsp;&nbsp; cum%<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0%&nbsp;&nbsp;&nbsp;&nbsp; 0%&nbsp;&nbsp; 291.02MB 99.83%&nbsp; runtime.goexit<br />
	&nbsp; 179.01MB 61.41% 61.41%&nbsp;&nbsp; 290.52MB 99.66%&nbsp; step5.BenchmarkHi<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0% 61.41%&nbsp;&nbsp; 290.52MB 99.66%&nbsp; testing.(*B).launch<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0% 61.41%&nbsp;&nbsp; 290.52MB 99.66%&nbsp; testing.(*B).runN<br />
	&nbsp;&nbsp; 26.50MB&nbsp; 9.09% 70.50%&nbsp;&nbsp; <b>111.51MB 38.25%&nbsp; step5.handleHi</b><br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0% 70.50%&nbsp;&nbsp;&nbsp; 85.01MB 29.16%&nbsp; bytes.(*Buffer).Write<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0% 70.50%&nbsp;&nbsp;&nbsp; 85.01MB 29.16%&nbsp; bytes.(*Buffer).grow<br />
	&nbsp;&nbsp; 85.01MB 29.16% 99.66%&nbsp;&nbsp;&nbsp; 85.01MB 29.16%&nbsp; bytes.makeSlice<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0% 99.66%&nbsp;&nbsp;&nbsp; 85.01MB 29.16%&nbsp; fmt.Fprintf<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0% 99.66%&nbsp;&nbsp;&nbsp; 85.01MB 29.16%&nbsp; net/http/httptest.(*ResponseRecorder).Write<br />
	(pprof) list handleHi<br />
	Total: 291.52MB<br />
	ROUTINE ======================== _/home1/tonybai/proj/opensource/github/experiments/go-debug-profile-optimization/step5.handleHi in /home1/tonybai/proj/opensource/github/experiments/go-debug-profile-optimization/step5/demo.go<br />
	&nbsp;&nbsp; 26.50MB&nbsp;&nbsp; 111.51MB (flat, cum) 38.25% of Total<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 17:&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; http.Error(w, &quot;Optional color is invalid&quot;, http.StatusBadRequest)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 18:&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 19:&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 20:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 21:&nbsp;&nbsp;&nbsp; visitNum := atomic.AddInt64(&amp;visitors, 1)<br />
	&nbsp;&nbsp; 26.50MB&nbsp;&nbsp; <b>111.51MB</b>&nbsp;&nbsp;&nbsp;&nbsp; 22:&nbsp;&nbsp;&nbsp; <b>fmt.Fprintf(</b>w, &quot;&lt;html&gt;&lt;h1 stype=&#39;color: \&quot;%s\&quot;&#39;&gt;Welcome!&lt;/h1&gt;You are visitor number %d!&quot;, r.FormValue(&quot;color&quot;), visitNum)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 23:}<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 24:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 25:func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 26:&nbsp;&nbsp;&nbsp; log.Printf(&quot;Starting on port 8080&quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 27:&nbsp;&nbsp;&nbsp; http.HandleFunc(&quot;/hi&quot;, handleHi)<br />
	(pprof)</font></p>
<p>内存从300MB降到111MB。内存来自哪？看到list handleHi，fmt.Fprintf分配了111.51MB。</p>
<p>我们来看这一行代码：<br />
	<font face="Courier New">fmt.Fprintf(w, &quot;&lt;h1 style=&#39;color: %s&#39;&gt;Welcome!&lt;/h1&gt;You are visitor number %d!&quot;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; r.FormValue(&quot;color&quot;), num)</font></p>
<p>fmt.Fprintf的manual：</p>
<p><font face="Courier New">$ go doc fmt.Fprintf<br />
	func Fprintf(w io.Writer, format string, a &#8230;interface{}) (n int, err error)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; Fprintf formats according to a format specifier and writes to w. It returns<br />
	&nbsp;&nbsp;&nbsp; the number of bytes written and any write error encountered.</font></p>
<p>这里回顾一下Go type在runtime中的内存占用：</p>
<p><font face="Courier New">A Go interface is 2 words of memory: (type, pointer).<br />
	A Go string is 2 words of memory: (base pointer, length)<br />
	A Go slice is 3 words of memory: (base pointer, length, capacity)</font></p>
<p>每次调用fmt.Fprintf，参数以value值形式传入函数时，程序就要为每个变参分配一个占用16bytes的empty interface，然后用传入的类型初始化该interface value。这就是这块累计分配内存较多的原因。</p>
<p><b>十一、</b><b>消除所有内存分配</b></p>
<p>下面的优化代码可能在实际中并不需要，但一旦真的成为瓶颈，可以这么做：</p>
<p><font face="Courier New">//go-debug-profile-optimization/step6/demo.go</font><br />
	&#8230; &#8230;<br />
	<font face="Courier New">var bufPool = sync.Pool{<br />
	&nbsp;&nbsp;&nbsp; New: func() interface{} {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return new(bytes.Buffer)<br />
	&nbsp;&nbsp;&nbsp; },<br />
	}</font></p>
<p><font face="Courier New">func handleHi(w http.ResponseWriter, r *http.Request) {<br />
	&nbsp;&nbsp;&nbsp; if !rxOptionalID.MatchString(r.FormValue(&quot;color&quot;)) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; http.Error(w, &quot;Optional color is invalid&quot;, http.StatusBadRequest)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; visitNum := atomic.AddInt64(&amp;visitors, 1)<br />
	&nbsp;&nbsp;&nbsp; buf := bufPool.Get().(*bytes.Buffer)<br />
	&nbsp;&nbsp;&nbsp; defer bufPool.Put(buf)<br />
	&nbsp;&nbsp;&nbsp; buf.Reset()<br />
	&nbsp;&nbsp;&nbsp; buf.WriteString(&quot;&lt;h1 style=&#39;color: &quot;)<br />
	&nbsp;&nbsp;&nbsp; buf.WriteString(r.FormValue(&quot;color&quot;))<br />
	&nbsp;&nbsp;&nbsp; buf.WriteString(&quot;&#39;&gt;Welcome!&lt;/h1&gt;You are visitor number &quot;)<br />
	&nbsp;&nbsp;&nbsp; b := strconv.AppendInt(buf.Bytes(), int64(visitNum), 10)<br />
	&nbsp;&nbsp;&nbsp; b = append(b, &#39;!&#39;)<br />
	&nbsp;&nbsp;&nbsp; w.Write(b)<br />
	}</font><br />
	&#8230; &#8230;</p>
<p><font face="Courier New">$&nbsp; go test -v -run=^$ -bench=^BenchmarkHi$ -benchtime=2s -memprofile=prof.mem<br />
	PASS<br />
	BenchmarkHi-4&nbsp;&nbsp;&nbsp; &nbsp;5000000&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 780 ns/op&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; 192 B/op&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 3 allocs/op<br />
	ok&nbsp; &nbsp;&nbsp;&nbsp; _/home1/tonybai/proj/opensource/github/experiments/go-debug-profile-optimization/step6&nbsp;&nbsp;&nbsp; 4.709s</font></p>
<p><font face="Courier New">&nbsp;go tool pprof &#8211;alloc_space step6.test prof.mem<br />
	Entering interactive mode (type &quot;help&quot; for commands)<br />
	(pprof) top &#8211;cum 10<br />
	1.07GB of 1.07GB total (&nbsp; 100%)<br />
	Dropped 5 nodes (cum &lt;= 0.01GB)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; flat&nbsp; flat%&nbsp;&nbsp; sum%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cum&nbsp;&nbsp; cum%<br />
	&nbsp;&nbsp;&nbsp; 1.07GB&nbsp;&nbsp; 100%&nbsp;&nbsp; 100%&nbsp;&nbsp;&nbsp;&nbsp; 1.07GB&nbsp;&nbsp; 100%&nbsp; step6.BenchmarkHi<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0%&nbsp;&nbsp; 100%&nbsp;&nbsp;&nbsp;&nbsp; 1.07GB&nbsp;&nbsp; 100%&nbsp; runtime.goexit<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0%&nbsp;&nbsp; 100%&nbsp;&nbsp;&nbsp;&nbsp; 1.07GB&nbsp;&nbsp; 100%&nbsp; testing.(*B).launch<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0%&nbsp;&nbsp; 100%&nbsp;&nbsp;&nbsp;&nbsp; 1.07GB&nbsp;&nbsp; 100%&nbsp; testing.(*B).runN</font></p>
<p><font face="Courier New">$ go test -bench=. -memprofile=prof.mem | tee mem.6<br />
	PASS<br />
	BenchmarkHi-4&nbsp;&nbsp;&nbsp; &nbsp;2000000&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 790 ns/op&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; 192 B/op&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 3 allocs/op<br />
	ok&nbsp; &nbsp;&nbsp;&nbsp; _/home1/tonybai/proj/opensource/github/experiments/go-debug-profile-optimization/step6&nbsp;&nbsp;&nbsp; 2.401s</font></p>
<p><font face="Courier New">$ benchcmp step5/mem.5 step6/mem.6<br />
	benchmark&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; old ns/op&nbsp;&nbsp;&nbsp;&nbsp; new ns/op&nbsp;&nbsp;&nbsp;&nbsp; delta<br />
	BenchmarkHi-4&nbsp;&nbsp;&nbsp;&nbsp; 1513&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 790&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -47.79%</font></p>
<p><font face="Courier New">benchmark&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; old allocs&nbsp;&nbsp;&nbsp;&nbsp; new allocs&nbsp;&nbsp;&nbsp;&nbsp; delta<br />
	BenchmarkHi-4&nbsp;&nbsp;&nbsp;&nbsp; 6&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 3&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -50.00%</font></p>
<p><font face="Courier New">benchmark&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; old bytes&nbsp;&nbsp;&nbsp;&nbsp; new bytes&nbsp;&nbsp;&nbsp;&nbsp; delta<br />
	BenchmarkHi-4&nbsp;&nbsp;&nbsp;&nbsp; 304&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 192&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -36.84%</font></p>
<p>可以看到handleHi已经不在top列表中了。benchcmp结果也显示内存分配又有大幅下降！</p>
<p><b>十二、竞争(Contention)优化</b></p>
<p>为handleHi编写一个Parallel benchmark test:</p>
<p><font face="Courier New">//go-debug-profile-optimization/step7/demo_test.go</font><br />
	<font face="Courier New">&#8230; &#8230;<br />
	func BenchmarkHiParallel(b *testing.B) {<br />
	&nbsp;&nbsp;&nbsp; r, err := http.ReadRequest(bufio.NewReader(strings.NewReader(&quot;GET / HTTP/1.0\r\n\r\n&quot;)))<br />
	&nbsp;&nbsp;&nbsp; if err != nil {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b.Fatal(err)<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; b.RunParallel(func(pb *testing.PB) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; rw := httptest.NewRecorder()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for pb.Next() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; handleHi(rw, r)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; reset(rw)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; })<br />
	}<br />
	&#8230; &#8230;</font></p>
<p>执行测试，并分析结果:</p>
<p><font face="Courier New">$ go test -bench=Parallel -blockprofile=prof.block<br />
	PASS<br />
	BenchmarkHiParallel-4&nbsp;&nbsp;&nbsp; &nbsp;5000000&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 305 ns/op<br />
	ok&nbsp; &nbsp;&nbsp;&nbsp; _/home1/tonybai/proj/opensource/github/experiments/go-debug-profile-optimization/step7&nbsp;&nbsp;&nbsp; 1.947s</font></p>
<p><font face="Courier New">$ go tool pprof step7.test&nbsp; prof.block<br />
	Entering interactive mode (type &quot;help&quot; for commands)<br />
	(pprof) top &#8211;cum 10<br />
	3.68s of 3.72s total (98.82%)<br />
	Dropped 29 nodes (cum &lt;= 0.02s)<br />
	Showing top 10 nodes out of 20 (cum &gt;= 1.84s)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; flat&nbsp; flat%&nbsp;&nbsp; sum%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cum&nbsp;&nbsp; cum%<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0%&nbsp;&nbsp;&nbsp;&nbsp; 0%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 3.72s&nbsp;&nbsp; 100%&nbsp; runtime.goexit<br />
	&nbsp;&nbsp;&nbsp;&nbsp; 1.84s 49.46% 49.46%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1.84s 49.46%&nbsp; runtime.chanrecv1<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0% 49.46%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1.84s 49.46%&nbsp; main.main<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0% 49.46%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1.84s 49.46%&nbsp; runtime.main<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0% 49.46%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1.84s 49.46%&nbsp; testing.(*M).Run<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0% 49.46%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1.84s 49.43%&nbsp; testing.(*B).run<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0% 49.46%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1.84s 49.43%&nbsp; testing.RunBenchmarks<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0% 49.46%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1.84s 49.36%&nbsp; step7.BenchmarkHiParallel<br />
	&nbsp;&nbsp;&nbsp;&nbsp; 1.84s 49.36% 98.82%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1.84s 49.36%&nbsp; sync.(*WaitGroup).Wait<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0% 98.82%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1.84s 49.36%&nbsp; testing.(*B).RunParallel<br />
	(pprof) list BenchmarkHiParallel<br />
	Total: 3.72s<br />
	ROUTINE ====== step7.BenchmarkHiParallel in step7/demo_test.go<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1.84s (flat, cum) 49.36% of Total<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp; 113:&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; rw := httptest.NewRecorder()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp; 114:&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; for pb.Next() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp; 115:&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; handleHi(rw, r)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp; 116:&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; reset(rw)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp; 117:&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1.84s&nbsp;&nbsp;&nbsp; 118:&nbsp;&nbsp;&nbsp; })<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp; 119:}<br />
	ROUTINE ==== step7.BenchmarkHiParallel.func1 in step7/demo_test.go<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp; 43.02ms (flat, cum)&nbsp; 1.16% of Total<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp; 110:&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp; 111:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp; 112:&nbsp;&nbsp;&nbsp; b.RunParallel(func(pb *testing.PB) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp; 113:&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; rw := httptest.NewRecorder()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp; 114:&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; for pb.Next() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp; 43.02ms&nbsp;&nbsp;&nbsp; 115:&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; handleHi(rw, r)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp; 116:&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; reset(rw)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp; 117:&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp; 118:&nbsp;&nbsp;&nbsp; })<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp; 119:}<br />
	(pprof) list handleHi<br />
	Total: 3.72s<br />
	ROUTINE =====step7.handleHi in step7/demo.go<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp; 43.02ms (flat, cum)&nbsp; 1.16% of Total<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 18:&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return new(bytes.Buffer)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 19:&nbsp;&nbsp;&nbsp; },<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 20:}<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 21:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 22:func handleHi(w http.ResponseWriter, r *http.Request) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp; 43.01ms&nbsp;&nbsp;&nbsp;&nbsp; 23:&nbsp;&nbsp;&nbsp; if !rxOptionalID.MatchString(r.FormValue(&quot;color&quot;)) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 24:&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; http.Error(w, &quot;Optional color is invalid&quot;, http.StatusBadRequest)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 25:&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 26:&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 27:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 28:&nbsp;&nbsp;&nbsp; visitNum := atomic.AddInt64(&amp;visitors, 1)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 2.50us&nbsp;&nbsp;&nbsp;&nbsp; 29:&nbsp;&nbsp;&nbsp; buf := bufPool.Get().(*bytes.Buffer)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 30:&nbsp;&nbsp;&nbsp; defer bufPool.Put(buf)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 31:&nbsp;&nbsp;&nbsp; buf.Reset()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 32:&nbsp;&nbsp;&nbsp; buf.WriteString(&quot;&lt;h1 style=&#39;color: &quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 33:&nbsp;&nbsp;&nbsp; buf.WriteString(r.FormValue(&quot;color&quot;))<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 34:&nbsp;&nbsp;&nbsp; buf.WriteString(&quot;&#39;&gt;Welcome!&lt;/h1&gt;You are visitor number &quot;)<br />
	(pprof)</font></p>
<p>handleHi中MatchString这块是一个焦点，这里耗时较多。</p>
<p>优化方法（<b>step8</b>）：</p>
<p><font face="Courier New">//go-debug-profile-optimization/step8/demo.go<br />
	&#8230; &#8230;<br />
	var colorRxPool = sync.Pool{<br />
	&nbsp;&nbsp;&nbsp; New: func() interface{} { return regexp.MustCompile(`\w*$`) },<br />
	}</font></p>
<p><font face="Courier New">func handleHi(w http.ResponseWriter, r *http.Request) {<br />
	&nbsp;&nbsp;&nbsp; if !<b>colorRxPool.Get().(*regexp.Regexp)</b>.MatchString(r.FormValue(&quot;color&quot;)) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; http.Error(w, &quot;Optional color is invalid&quot;, http.StatusBadRequest)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; visitNum := atomic.AddInt64(&amp;visitors, 1)<br />
	&nbsp;&nbsp;&nbsp; buf := bufPool.Get().(*bytes.Buffer)<br />
	&nbsp;&nbsp;&nbsp; defer bufPool.Put(buf)<br />
	&nbsp;&nbsp;&nbsp; buf.Reset()<br />
	&nbsp;&nbsp;&nbsp; buf.WriteString(&quot;&lt;h1 style=&#39;color: &quot;)<br />
	&nbsp;&nbsp;&nbsp; buf.WriteString(r.FormValue(&quot;color&quot;))<br />
	&nbsp;&nbsp;&nbsp; buf.WriteString(&quot;&#39;&gt;Welcome!&lt;/h1&gt;You are visitor number &quot;)<br />
	&nbsp;&nbsp;&nbsp; b := strconv.AppendInt(buf.Bytes(), int64(visitNum), 10)<br />
	&nbsp;&nbsp;&nbsp; b = append(b, &#39;!&#39;)<br />
	&nbsp;&nbsp;&nbsp; w.Write(b)<br />
	}<br />
	&#8230; &#8230;</font></p>
<p>测试执行与分析：</p>
<p><font face="Courier New">$ go test -bench=Parallel -blockprofile=prof.block<br />
	PASS<br />
	BenchmarkHiParallel-4&nbsp;&nbsp;&nbsp; &nbsp; 100000&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; 19190 ns/op<br />
	ok&nbsp; &nbsp;&nbsp;&nbsp; _/home1/tonybai/proj/opensource/github/experiments/go-debug-profile-optimization/step8&nbsp;&nbsp;&nbsp; 2.219s</font></p>
<p><font face="Courier New">$ go tool pprof step8.test&nbsp; prof.block<br />
	Entering interactive mode (type &quot;help&quot; for commands)<br />
	(pprof) top &#8211;cum 10<br />
	4.22s of 4.23s total (99.69%)<br />
	Dropped 28 nodes (cum &lt;= 0.02s)<br />
	Showing top 10 nodes out of 12 (cum &gt;= 2.11s)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; flat&nbsp; flat%&nbsp;&nbsp; sum%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cum&nbsp;&nbsp; cum%<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0%&nbsp;&nbsp;&nbsp;&nbsp; 0%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 4.23s&nbsp;&nbsp; 100%&nbsp; runtime.goexit<br />
	&nbsp;&nbsp;&nbsp;&nbsp; 2.11s 49.90% 49.90%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2.11s 49.90%&nbsp; runtime.chanrecv1<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0% 49.90%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2.11s 49.89%&nbsp; main.main<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0% 49.90%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2.11s 49.89%&nbsp; runtime.main<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0% 49.90%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2.11s 49.89%&nbsp; testing.(*M).Run<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0% 49.90%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2.11s 49.86%&nbsp; testing.(*B).run<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0% 49.90%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2.11s 49.86%&nbsp; testing.RunBenchmarks<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0% 49.90%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2.11s 49.79%&nbsp; step8.BenchmarkHiParallel<br />
	&nbsp;&nbsp;&nbsp;&nbsp; 2.11s 49.79% 99.69%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2.11s 49.79%&nbsp; sync.(*WaitGroup).Wait<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0% 99.69%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2.11s 49.79%&nbsp; testing.(*B).RunParallel<br />
	(pprof) list BenchmarkHiParallel<br />
	Total: 4.23s<br />
	ROUTINE ======step8.BenchmarkHiParallel in step8/demo_test.go<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2.11s (flat, cum) 49.79% of Total<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp; 113:&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; rw := httptest.NewRecorder()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp; 114:&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; for pb.Next() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp; 115:&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; handleHi(rw, r)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp; 116:&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; reset(rw)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp; 117:&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2.11s&nbsp;&nbsp;&nbsp; 118:&nbsp;&nbsp;&nbsp; })<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp; 119:}<br />
	ROUTINE ======step8.BenchmarkHiParallel.func1 in step8/demo_test.go<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp; 11.68ms (flat, cum)&nbsp; 0.28% of Total<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp; 110:&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp; 111:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp; 112:&nbsp;&nbsp;&nbsp; b.RunParallel(func(pb *testing.PB) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp; 113:&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; rw := httptest.NewRecorder()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp; 114:&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; for pb.Next() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp; 11.68ms&nbsp;&nbsp;&nbsp; 115:&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; handleHi(rw, r)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp; 116:&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; reset(rw)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp; 117:&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp; 118:&nbsp;&nbsp;&nbsp; })<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp; 119:}<br />
	(pprof) list handleHi<br />
	Total: 4.23s<br />
	ROUTINE ======step8.handleHi in step8/demo.go<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp; 11.68ms (flat, cum)&nbsp; 0.28% of Total<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 21:var colorRxPool = sync.Pool{<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 22:&nbsp;&nbsp;&nbsp; New: func() interface{} { return regexp.MustCompile(`\w*$`) },<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 23:}<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 24:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 25:func handleHi(w http.ResponseWriter, r *http.Request) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 5.66ms&nbsp;&nbsp;&nbsp;&nbsp; 26:&nbsp;&nbsp;&nbsp; if !colorRxPool.Get().(*regexp.Regexp).MatchString(r.FormValue(&quot;color&quot;)) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 27:&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; http.Error(w, &quot;Optional color is invalid&quot;, http.StatusBadRequest)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 28:&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 29:&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 30:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 31:&nbsp;&nbsp;&nbsp; visitNum := atomic.AddInt64(&amp;visitors, 1)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 6.02ms&nbsp;&nbsp;&nbsp;&nbsp; 32:&nbsp;&nbsp;&nbsp; buf := bufPool.Get().(*bytes.Buffer)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 33:&nbsp;&nbsp;&nbsp; defer bufPool.Put(buf)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 34:&nbsp;&nbsp;&nbsp; buf.Reset()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 35:&nbsp;&nbsp;&nbsp; buf.WriteString(&quot;&lt;h1 style=&#39;color: &quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 36:&nbsp;&nbsp;&nbsp; buf.WriteString(r.FormValue(&quot;color&quot;))<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .&nbsp;&nbsp;&nbsp;&nbsp; 37:&nbsp;&nbsp;&nbsp; buf.WriteString(&quot;&#39;&gt;Welcome!&lt;/h1&gt;You are visitor number &quot;)<br />
	(pprof)</font></p>
<p>优化后，MatchString从43ms降到5.66ms。</p>
<p style='text-align:left'>&copy; 2015, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2015/08/25/go-debugging-profiling-optimization/feed/</wfw:commentRss>
		<slash:comments>18</slash:comments>
		</item>
		<item>
		<title>Golang跨平台交叉编译</title>
		<link>https://tonybai.com/2014/10/20/cross-compilation-with-golang/</link>
		<comments>https://tonybai.com/2014/10/20/cross-compilation-with-golang/#comments</comments>
		<pubDate>Mon, 20 Oct 2014 08:40:52 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[6g]]></category>
		<category><![CDATA[6l]]></category>
		<category><![CDATA[Build]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[Cgo]]></category>
		<category><![CDATA[Compile]]></category>
		<category><![CDATA[Compiler]]></category>
		<category><![CDATA[Darwin]]></category>
		<category><![CDATA[GCC]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[GOARCH]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[GOOS]]></category>
		<category><![CDATA[GOROOT]]></category>
		<category><![CDATA[linker]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Opensource]]></category>
		<category><![CDATA[开源]]></category>
		<category><![CDATA[编译器]]></category>
		<category><![CDATA[链接器]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=1571</guid>
		<description><![CDATA[近期在某本书上看到Go跨平台交叉编译的强大功能，于是想自己测试一下。以下记录了测试过程以及一些结论，希望能给大家带来帮助。 我的Linux环境如下： uname -a Linux ubuntu-Server-14 3.13.0-32-generic #57-Ubuntu SMP Tue Jul 15 03:51:08 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux $ go version go version go1.3.1 linux/amd64 跨平台交叉编译涉及两个重要的环境变量：GOOS和GOARCH，分别代表Target Host OS和Target Host ARCH，如果没有显式设置这些环境变量，我们通过go env可以看到go编译器眼中这两个环境变量的当前值： $ go env GOARCH=&#34;amd64&#34; GOOS=&#34;linux&#34; GOHOSTARCH=&#34;amd64&#34; GOHOSTOS=&#34;linux&#34; &#8230; &#8230; 这里还有两个变量GOHOSTOS和GOHOSTARCH，分别表示的是当前所在主机的的OS和CPU ARCH。我的Go是采用安装包安装的，因此默认情况下，这两组环境变量的值都是来自当前主机的信息。 现在我们就来交叉编译一下：在linux/amd64平台下利用Go编译器编译一个可以运行在linux/amd64下的程序，样例程序如下： //testport.go package main import ( &#160;&#160;&#160;&#160;&#160;&#160;&#160; &#34;fmt&#34; &#160;&#160;&#160;&#160;&#160;&#160;&#160; &#34;os/exec&#34; &#160;&#160;&#160;&#160;&#160;&#160;&#160; &#34;bytes&#34; [...]]]></description>
			<content:encoded><![CDATA[<p>近期在某本书上看到<a href="http://golang.org">Go</a>跨平台交叉编译的强大功能，于是想自己测试一下。以下记录了测试过程以及一些结论，希望能给大家带来帮助。</p>
<p>我的Linux环境如下：</p>
<p><font face="Courier New">uname -a<br />
	Linux ubuntu-Server-14 3.13.0-32-generic #57-Ubuntu SMP Tue Jul 15 03:51:08 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux</font></p>
<p><font face="Courier New">$ go version<br />
	go version go1.3.1 linux/amd64</font></p>
<p>跨平台交叉编译涉及两个重要的环境变量：GOOS和GOARCH，分别代表Target Host OS和Target Host ARCH，如果没有显式设置这些环境变量，我们通过go env可以看到go编译器眼中这两个环境变量的当前值：</p>
<p><font face="Courier New">$ go env<br />
	GOARCH=&quot;amd64&quot;<br />
	GOOS=&quot;linux&quot;</font><br />
	<font face="Courier New">GOHOSTARCH=&quot;amd64&quot;<br />
	GOHOSTOS=&quot;linux&quot;</font><br />
	&#8230; &#8230;</p>
<p>这里还有两个变量GOHOSTOS和GOHOSTARCH，分别表示的是当前所在主机的的OS和CPU ARCH。我的Go是采用安装包安装的，因此默认情况下，这两组环境变量的值都是来自当前主机的信息。</p>
<p>现在我们就来交叉编译一下：在linux/amd64平台下利用Go编译器编译一个可以运行在linux/amd64下的程序，样例程序如下：</p>
<p><font face="Courier New">//testport.go<br />
	package main</font></p>
<p><font face="Courier New">import (<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;fmt&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;os/exec&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;bytes&quot;<br />
	)</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cmd := exec.Command(&quot;uname&quot;, &quot;-a&quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var out bytes.Buffer<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cmd.Stdout = &amp;out</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; err := cmd.Run()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if err != nil {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;Err when executing uname command&quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;I am running on&quot;, out.String())<br />
	}</font></p>
<p>在Linux/amd64下编译运行：</p>
<p><font face="Courier New">$ go build -o testport_linux testport.go<br />
	$ testport_linux<br />
	I am running on Linux ubuntu-Server-14 3.13.0-32-generic #57-Ubuntu SMP Tue Jul 15 03:51:08 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux</font></p>
<p>接下来，我们来尝试在Linux/amd64上编译一个可以运行在darwin/amd64上的程序。我只需修改GOOS和GOARCH两个标识目标主机OS和ARCH的环境变量：</p>
<p><font face="Courier New">$ GOOS=darwin GOARCH=amd64 go build -o testport_darwin testport.go<br />
	go build runtime: darwin/amd64 must be bootstrapped using make.bash</font></p>
<p>编译器报错了！提示darwin/amd64必须通过make.bash重新装载。显然，通过安装包安装到linux/amd64下的Go编译器还无法直接交叉编译出darwin/amd64下可以运行的程序，我们需要做一些准备工作。我们找找make.bash在哪里！</p>
<p>我们到Go的$GOROOT路径下去找make.bash，Go的安装路径下的组织很简约，扫一眼便知make.sh大概在$GOROOT/src下，打开make.sh，我们在文件头处看到如下一些内容：</p>
<p><font face="Courier New"># Environment variables that control make.bash:<br />
	#<br />
	# GOROOT_FINAL: The expected final Go root, baked into binaries.<br />
	# The default is the location of the Go tree during the build.<br />
	#<br />
	# GOHOSTARCH: The architecture for host tools (compilers and<br />
	# binaries).&nbsp; Binaries of this type must be executable on the current<br />
	# system, so the only common reason to set this is to set<br />
	# GOHOSTARCH=386 on an amd64 machine.<br />
	#<br />
	# GOARCH: The target architecture for installed packages and tools.<br />
	#<br />
	# GOOS: The target operating system for installed packages and tools.</font><br />
	&#8230; &#8230;</p>
<p>make.bash头并未简要说明文件的用途，但名为make.xx的文件想必是用来构建Go编译工具的。这里提到几个环境变量可以控制 make.bash的行为，显然GOARCH和GOOS更能引起我们的兴趣。我们再回过头来输出testport.go编译过程的详细信息：</p>
<p><font face="Courier New">$ go build -x -o testport_linux testport.go<br />
	WORK=/tmp/go-build286732099<br />
	mkdir -p $WORK/command-line-arguments/_obj/<br />
	cd /home/tonybai/Test/Go/porting<br />
	/usr/local/go/pkg/tool/linux_amd64/6g -o $WORK/command-line-arguments.a -trimpath $WORK -p command-line-arguments -complete -D _/home/tonybai/Test/Go/porting -I $WORK -pack ./testport.go<br />
	cd .<br />
	/usr/local/go/pkg/tool/linux_amd64/6l -o testport_linux -L $WORK -extld=gcc $WORK/command-line-arguments.a</font></p>
<p>我们发现Go实际上用的是$GOROOT/pkg/tool/linux_amd64下的6g（编译器）和6l（链接器）来完成整个编译过程的，看到6g 和6l所在目录名为linux_amd64，我们可以大胆猜测编译darwin/amd64 go程序应该使用的是$GOROOT/pkg/tool/darwin_amd64下的工具。不过在我在$GOROOT/pkg/tool下没有发现 darwin_amd64目录，也就是说我们通过安装包安装的Go仅自带了for linux_amd64的编译工具，要想交叉编译出for darwin_amd64的程序，我们需要通过make.bash来手工编译出这些工具。</p>
<p><font face="Courier New">tonybai@ubuntu-Server-14:/usr/local/go/pkg$ ls<br />
	linux_amd64&nbsp; linux_amd64_race&nbsp; obj&nbsp; tool</font></p>
<p><font face="Courier New">tonybai@ubuntu-Server-14:/usr/local/go/pkg/tool$ ls<br />
	linux_amd64</font></p>
<p>根据前面make.bash的用法说明，我们来尝试构建一下：</p>
<p><font face="Courier New">cd $GOROOT/src<br />
	sudo GOOS=darwin GOARCH=amd64 ./make.bash</font></p>
<p><font face="Courier New"># Building C bootstrap tool.<br />
	cmd/dist</font></p>
<p><font face="Courier New"># Building compilers and Go bootstrap tool for host, linux/amd64.<br />
	&#8230; &#8230;<br />
	cmd/cc<br />
	cmd/gc<br />
	cmd/6l<br />
	cmd/6a<br />
	cmd/6c<br />
	cmd/6g<br />
	pkg/runtime<br />
	&#8230; &#8230;<br />
	cmd/go<br />
	pkg/runtime (darwin/amd64)</font></p>
<p><font face="Courier New"># Building packages and commands for host, linux/amd64.<br />
	runtime<br />
	&#8230; &#8230;<br />
	text/scanner</font></p>
<p><font face="Courier New"># Building packages and commands for darwin/amd64.<br />
	runtime<br />
	errors<br />
	&#8230; &#8230;<br />
	testing/quick<br />
	text/scanner</font></p>
<p><font face="Courier New">&#8212;<br />
	Installed Go for darwin/amd64 in /usr/local/go<br />
	Installed commands in /usr/local/go/bin</font></p>
<p>编译后，我们再来试试编译for darwin_amd64的程序：</p>
<p><font face="Courier New">$ GOOS=darwin GOARCH=amd64 go build -x -o testport_darwin testport.go<br />
	WORK=/tmp/go-build972764136<br />
	mkdir -p $WORK/command-line-arguments/_obj/<br />
	cd /home/tonybai/Test/Go/porting<br />
	/usr/local/go/pkg/tool/linux_amd64/6g -o $WORK/command-line-arguments.a -trimpath $WORK -p command-line-arguments -complete -D _/home/tonybai/Test/Go/porting -I $WORK -pack ./testport.go<br />
	cd .<br />
	/usr/local/go/pkg/tool/linux_amd64/6l -o testport_darwin -L $WORK -extld=gcc $WORK/command-line-arguments.a</font></p>
<p>将文件copy到我的Mac Air下执行：</p>
<p><font face="Courier New">$chmod +x testport_darwin<br />
	$testport_darwin<br />
	I am running on Darwin TonydeMacBook-Air.local 13.1.0 Darwin Kernel Version 13.1.0: Thu Jan 16 19:40:37 PST 2014; root:xnu-2422.90.20~2/RELEASE_X86_64 x86_64</font></p>
<p>编译虽然成功了，但从-x输出的详细编译过程来看，Go编译连接使用的工具依旧是linux_amd64下的6g和6l，为什么没有使用darwin_amd64下的6g和6l呢？原来$GOROOT/pkg/tool/darwin_amd64下根本就没有6g和6l：</p>
<p><font face="Courier New">/usr/local/go/pkg/tool/darwin_amd64$ ls<br />
	addr2line&nbsp; cgo&nbsp; fix&nbsp; nm&nbsp; objdump&nbsp; pack&nbsp; yac</font>c</p>
<p>但查看一下<font face="Courier New">pkg/tool/linux_amd64/</font>下程序的更新时间：</p>
<p>/usr/local/go/pkg/tool/linux_amd64$ ls -l<br />
	<font face="Courier New">&#8230; &#8230;<br />
	-rwxr-xr-x 1 root root 2482877 10月 20 15:12 6g<br />
	-rwxr-xr-x 1 root root 1186445 10月 20 15:12 6l<br />
	&#8230; &#8230;</font></p>
<p>我们发现6g和6l都是被刚才的make.bash新编译出来的，我们可以得出结论：新6g和新6l目前既可以编译本地程序（linux/amd64)，也可以编译darwin/amd64下的程序了，例如重新编译testport_linux依旧ok：</p>
<p><font face="Courier New">$ go build -x -o testport_linux testport.go<br />
	WORK=/tmp/go-build636762567<br />
	mkdir -p $WORK/command-line-arguments/_obj/<br />
	cd /home/tonybai/Test/Go/porting<br />
	/usr/local/go/pkg/tool/linux_amd64/6g -o $WORK/command-line-arguments.a -trimpath $WORK -p command-line-arguments -complete -D _/home/tonybai/Test/Go/porting -I $WORK -pack ./testport.go<br />
	cd .<br />
	/usr/local/go/pkg/tool/linux_amd64/6l -o testport_linux -L $WORK -extld=gcc $WORK/command-line-arguments.a</font></p>
<p>如果我们还想给Go编译器加上交叉编译windows/amd64程序的功能，我们再执行一次make.bash：</p>
<p><font face="Courier New">sudo GOOS=windows GOARCH=amd64 ./make.bash</font></p>
<p>编译成功后，我们来编译一下Windows程序：</p>
<p><font face="Courier New">$ GOOS=windows GOARCH=amd64 go build -x -o testport_windows.exe testport.go<br />
	WORK=/tmp/go-build626615350<br />
	mkdir -p $WORK/command-line-arguments/_obj/<br />
	cd /home/tonybai/Test/Go/porting<br />
	/usr/local/go/pkg/tool/linux_amd64/6g -o $WORK/command-line-arguments.a -trimpath $WORK -p command-line-arguments -complete -D _/home/tonybai/Test/Go/porting -I $WORK -pack ./testport.go<br />
	cd .<br />
	/usr/local/go/pkg/tool/linux_amd64/6l -o testport_windows.exe -L $WORK -extld=gcc $WORK/command-line-arguments.a</font></p>
<p>把testport_windows.exe扔到Windows上执行，结果：</p>
<p><font face="Courier New">Err when executing uname command</font></p>
<p>显然Windows下没有uname命令，提示执行出错。</p>
<p>至此，我的Go编译器具备了在Linux下编译windows/amd64和darwin/amd64的能力。如果你还想增加其他平台的能力，就像上面那样操作执行make.bash即可。</p>
<p>如果在go源文件中有与C语言的交互代码，那么交叉编译功能是否还能奏效呢？毕竟C在各个平台上的运行库、链接库等都是不同的。我们先来看看这个例子，我们使用之前在《<a href="http://tonybai.com/2014/10/12/discussion-on-shared-mem-support-in-docker/">探讨docker容器对共享内存的支持情况</a>》一文中的一个例子：</p>
<p><font face="Courier New">//testport_cgoenabled.go<br />
	package main</font></p>
<p><font face="Courier New">//#include &lt;stdio.h&gt;<br />
	//#include &lt;sys/types.h&gt;<br />
	//#include &lt;sys/mman.h&gt;<br />
	//#include &lt;fcntl.h&gt;<br />
	//<br />
	//#define SHMSZ&nbsp;&nbsp;&nbsp;&nbsp; 27<br />
	//<br />
	//int shm_rd()<br />
	//{<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char c;<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char *shm = NULL;<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char *s = NULL;<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int fd;<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if ((fd = open(&quot;./shm.txt&quot;, O_RDONLY)) == -1)&nbsp; {<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return -1;<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	//<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; shm = (char*)mmap(shm, SHMSZ, PROT_READ, MAP_SHARED, fd, 0);<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (!shm) {<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return -2;<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	//<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(fd);<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; s = shm;<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int i = 0;<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for (i = 0; i &lt; SHMSZ &#8211; 1; i++) {<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf(&quot;%c &quot;, *(s + i));<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf(&quot;\n&quot;);<br />
	//<br />
	//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return 0;<br />
	//}<br />
	import &quot;C&quot;</font></p>
<p><font face="Courier New">import &quot;fmt&quot;</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; i := C.shm_rd()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if i != 0 {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;Mmap Share Memory Read Error:&quot;, i)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;Mmap Share Memory Read Ok&quot;)<br />
	}</font></p>
<p>我们先编译出一个本地可运行的程序：</p>
<p><font face="Courier New">$ go build -x -o testport_cgoenabled_linux testport_cgoenabled.go<br />
	WORK=/tmp/go-build977176241<br />
	mkdir -p $WORK/command-line-arguments/_obj/<br />
	cd /home/tonybai/Test/Go/porting<br />
	CGO_LDFLAGS=&quot;-g&quot; &quot;-O2&quot; /usr/local/go/pkg/tool/linux_amd64/cgo -objdir $WORK/command-line-arguments/_obj/ &#8212; -I $WORK/command-line-arguments/_obj/ testport_cgoenabled.go<br />
	/usr/local/go/pkg/tool/linux_amd64/6c -F -V -w -trimpath $WORK -I $WORK/command-line-arguments/_obj/ -I /usr/local/go/pkg/linux_amd64 -o $WORK/command-line-arguments/_obj/_cgo_defun.6 -D GOOS_linux -D GOARCH_amd64 $WORK/command-line-arguments/_obj/_cgo_defun.c<br />
	gcc -I . -fPIC -m64 -pthread -fmessage-length=0 -print-libgcc-file-name<br />
	gcc -I . -fPIC -m64 -pthread -fmessage-length=0 -I $WORK/command-line-arguments/_obj/ -g -O2 -o $WORK/command-line-arguments/_obj/_cgo_main.o -c $WORK/command-line-arguments/_obj/_cgo_main.c<br />
	gcc -I . -fPIC -m64 -pthread -fmessage-length=0 -I $WORK/command-line-arguments/_obj/ -g -O2 -o $WORK/command-line-arguments/_obj/_cgo_export.o -c $WORK/command-line-arguments/_obj/_cgo_export.c<br />
	gcc -I . -fPIC -m64 -pthread -fmessage-length=0 -I $WORK/command-line-arguments/_obj/ -g -O2 -o $WORK/command-line-arguments/_obj/testport_cgoenabled.cgo2.o -c $WORK/command-line-arguments/_obj/testport_cgoenabled.cgo2.c<br />
	gcc -I . -fPIC -m64 -pthread -fmessage-length=0 -o $WORK/command-line-arguments/_obj/_cgo_.o $WORK/command-line-arguments/_obj/_cgo_main.o $WORK/command-line-arguments/_obj/_cgo_export.o $WORK/command-line-arguments/_obj/testport_cgoenabled.cgo2.o -g -O2<br />
	/usr/local/go/pkg/tool/linux_amd64/cgo -objdir $WORK/command-line-arguments/_obj/ -dynimport $WORK/command-line-arguments/_obj/_cgo_.o -dynout $WORK/command-line-arguments/_obj/_cgo_import.c<br />
	/usr/local/go/pkg/tool/linux_amd64/6c -F -V -w -trimpath $WORK -I $WORK/command-line-arguments/_obj/ -I /usr/local/go/pkg/linux_amd64 -o $WORK/command-line-arguments/_obj/_cgo_import.6 -D GOOS_linux -D GOARCH_amd64 $WORK/command-line-arguments/_obj/_cgo_import.c<br />
	gcc -I . -fPIC -m64 -pthread -fmessage-length=0 -o $WORK/command-line-arguments/_obj/_all.o $WORK/command-line-arguments/_obj/_cgo_export.o $WORK/command-line-arguments/_obj/testport_cgoenabled.cgo2.o -g -O2 -Wl,-r -nostdlib /usr/lib/gcc/x86_64-linux-gnu/4.8/libgcc.a<br />
	/usr/local/go/pkg/tool/linux_amd64/6g -o $WORK/command-line-arguments.a -trimpath $WORK -p command-line-arguments -D _/home/tonybai/Test/Go/porting -I $WORK -pack $WORK/command-line-arguments/_obj/_cgo_gotypes.go $WORK/command-line-arguments/_obj/testport_cgoenabled.cgo1.go<br />
	pack r $WORK/command-line-arguments.a $WORK/command-line-arguments/_obj/_cgo_import.6 $WORK/command-line-arguments/_obj/_cgo_defun.6 $WORK/command-line-arguments/_obj/_all.o # internal<br />
	cd .<br />
	/usr/local/go/pkg/tool/linux_amd64/6l -o testport_cgoenabled_linux -L $WORK -extld=gcc $WORK/command-line-arguments.a</font></p>
<p>输出了好多日志！不过可以看出Go编译器先调用CGO对Go源码中的C代码进行了编译，然后才是常规的Go编译，最后通过6l链接在一起。Cgo似乎直接使用了Gcc。我们再来试试跨平台编译：</p>
<p><font face="Courier New">$ GOOS=darwin GOARCH=amd64 go build -x -o testport_cgoenabled_darwin testport_cgoenabled.go<br />
	WORK=/tmp/go-build124869433<br />
	can&#39;t load package: no buildable Go source files in /home/tonybai/Test/Go/porting</font></p>
<p>当我们编译for Darwin/amd64平台的程序时，Go无法像之前那样的顺利完成编译，而是提示错误。从网上给出的资料来看，如果Go源码中包含C互操作代码，那么 目前依旧无法实现交叉编译，因为cgo会直接使用各个平台的本地c编译器去编译Go文件中的C代码。默认情况下，make.bash会置 CGO_ENABLED=0。</p>
<p>如果你非要将CGO_ENABLED设置为1去编译go的话，至少我得到了如下错误，导致无法编译通过：</p>
<p><font face="Courier New">$ sudo CGO_ENABLED=1 GOOS=darwin GOARCH=amd64 ./make.bash &#8211;no-clean<br />
	&#8230; &#8230;<br />
	# Building packages and commands for darwin/amd64.<br />
	&#8230; &#8230;<br />
	37: error: &#39;AI_MASK&#39; undeclared (first use in this function)</font><br />
	&nbsp;</p>
<p style='text-align:left'>&copy; 2014, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2014/10/20/cross-compilation-with-golang/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
	</channel>
</rss>
