<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.9.2">Jekyll</generator><link href="/feed.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2023-01-13T05:15:54+00:00</updated><id>/feed.xml</id><title type="html">Ruminations on Computer System</title><subtitle>A collection of computer system's design and programming</subtitle><entry><title type="html">Go语言defer的作用域为当前函数</title><link href="/programming/2023/01/10/go-defer.html" rel="alternate" type="text/html" title="Go语言defer的作用域为当前函数" /><published>2023-01-10T00:00:00+00:00</published><updated>2023-01-10T00:00:00+00:00</updated><id>/programming/2023/01/10/go-defer</id><content type="html" xml:base="/programming/2023/01/10/go-defer.html">&lt;p&gt;Go官方对&lt;a href=&quot;https://go.dev/ref/spec#Defer_statements&quot;&gt;defer关键字&lt;/a&gt;的行为解释，第一句是：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;A “defer” statement invokes a function whose execution is deferred to the moment the surrounding function returns&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;defer的作用域是一个函数，在函数退出时立即调用。
在任何&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{ }&lt;/code&gt;包围的区间里写defer，效果等同于在该执行点注册一次defer。&lt;/p&gt;

&lt;h2 id=&quot;1-在循环和if语句中注册defer&quot;&gt;1 在循环和if语句中注册defer&lt;/h2&gt;

&lt;p&gt;考虑下面的代码会打印什么内容？&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;fmt&quot;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;

	&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;defer&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;itr&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Printf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;finish loop %d&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;itr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;}(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;defer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Printf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;finish if&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Printf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Yes&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;n&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Printf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;end proc&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;结果是，先打印”Yes”和”end proc”，之后再按照defer注册的逆序打印相应内容，即defer的调用顺序。如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Yes
end proc
finish if
finish loop 6
finish loop 5
finish loop 4
finish loop 3
finish loop 2
finish loop 1
finish loop 0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;2-在多次运行行为不同的代码块中注册defer&quot;&gt;2 在多次运行行为不同的代码块中注册defer&lt;/h2&gt;

&lt;p&gt;现在考虑如下代码。和上面的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if&lt;/code&gt;语句不同的是，由于&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rand&lt;/code&gt;存在，程序每次运行的行为都可能与上一次不同。
那么&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;defer&lt;/code&gt;的注册是否也可以是有选择的呢？&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
	&lt;span class=&quot;s&quot;&gt;&quot;fmt&quot;&lt;/span&gt;
	&lt;span class=&quot;s&quot;&gt;&quot;math/rand&quot;&lt;/span&gt;
	&lt;span class=&quot;s&quot;&gt;&quot;time&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;rand&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Seed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Now&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UnixMicro&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rand&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;defer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Printf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;random number is odd&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;n&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Printf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;end proc, r%%2 = %v&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;程序运行两次，产生不同的随机数与不同的行为：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;end proc, r%2 = 1
random number is odd

end proc, r%2 = 0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;所以可以得出结论，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;defer&lt;/code&gt;在代码块中的注册也是有选择的，
没有运行到的代码是不会对&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;defer&lt;/code&gt;注册的。&lt;/p&gt;</content><author><name></name></author><category term="programming" /><summary type="html">Go官方对defer关键字的行为解释，第一句是：</summary></entry><entry><title type="html">Go语言channel的输出顺序</title><link href="/programming/2022/12/23/go-channel-order.html" rel="alternate" type="text/html" title="Go语言channel的输出顺序" /><published>2022-12-23T00:00:00+00:00</published><updated>2022-12-23T00:00:00+00:00</updated><id>/programming/2022/12/23/go-channel-order</id><content type="html" xml:base="/programming/2022/12/23/go-channel-order.html">&lt;p&gt;关于有缓冲和无缓冲&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;channel&lt;/code&gt;，网上有许多介绍。
简单来讲，有缓冲的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;channel&lt;/code&gt;提供了一个有限长度的有序通知队列，生产者在队列充满前都是无阻塞的向里面填入内容，
且当队列非空时消费者可以无阻塞的从&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;channel&lt;/code&gt;中接收通知，队列空时消费者阻塞。
而无缓冲的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;channel&lt;/code&gt;提供的是阻塞的通知机制，生产者在向其中填入内容时可能会阻塞，
阻塞的条件是消费者此时是否已经阻塞在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;channel&lt;/code&gt;上等待接收，如果没有则生产者阻塞。&lt;/p&gt;

&lt;p&gt;但是，关于无缓冲&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;channel&lt;/code&gt;的通知顺序，网上没有太多介绍。
本文通过简单的实验确认了，无缓冲&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;channel&lt;/code&gt;的通知顺序同样是按照生产者发出通知的顺序来的。
不过如果生产者本身发起通知的顺序不定，则&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;channel&lt;/code&gt;的输出顺序也会不定。&lt;/p&gt;

&lt;h2 id=&quot;1-生产者按序写入channel&quot;&gt;1 生产者按序写入channel&lt;/h2&gt;

&lt;p&gt;实验代码如下。这段代码对一个无缓冲&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;channel&lt;/code&gt;中按照每100ms的间隔向里面写入，标号顺序为0~9。
在等待足够长的时间之后（2s），再反复从&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;channel&lt;/code&gt;取出标号。&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
	&lt;span class=&quot;s&quot;&gt;&quot;fmt&quot;&lt;/span&gt;
	&lt;span class=&quot;s&quot;&gt;&quot;time&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;ch&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;make&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;chan&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

	&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;go&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;idx&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;ch&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;idx&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;}(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Sleep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Millisecond&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Sleep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Second&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

	&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Printf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;%v&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;反复运行，可以看到总是有如下输出：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ go run main.go
0
1
2
3
4
5
6
7
8
9
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;说明无缓冲&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;channel&lt;/code&gt;的通知顺序同样是按照生产者发出通知的顺序来的。&lt;/p&gt;

&lt;h2 id=&quot;2-生产者无序写入channel&quot;&gt;2 生产者无序写入channel&lt;/h2&gt;

&lt;p&gt;使用强制睡眠并不能反映真实代码中常见的多&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;goroutine&lt;/code&gt;竞争&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;channel&lt;/code&gt;的场景。
于是，去掉上述代码的部门睡眠，得到如下代码：&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
	&lt;span class=&quot;s&quot;&gt;&quot;fmt&quot;&lt;/span&gt;
	&lt;span class=&quot;s&quot;&gt;&quot;time&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;ch&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;make&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;chan&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

	&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;go&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;idx&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;ch&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;idx&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;}(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Sleep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Second&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

	&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Printf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;%v&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;运行可以发现，输出的顺序并没有按照0~9的顺序来。
由于&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;goroutine&lt;/code&gt;的启动与调度顺序并不完全按照代码调用顺序来，所以&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;channel&lt;/code&gt;的输出也不会按照0~9的顺序出现。
实际打印出的顺序就是生产者&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;goroutine&lt;/code&gt;中写入&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;channel&lt;/code&gt;的顺序。
多次运行，其中两次的结果如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ go run main.go
3
0
1
2
6
4
5
7
8
9
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ go run main.go
9
0
1
2
3
4
5
6
7
8
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;本文通过代码实验了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;channel&lt;/code&gt;的输出顺序，确定了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;channel&lt;/code&gt;具有保序的特性。&lt;/p&gt;</content><author><name></name></author><category term="programming" /><summary type="html">关于有缓冲和无缓冲channel，网上有许多介绍。 简单来讲，有缓冲的channel提供了一个有限长度的有序通知队列，生产者在队列充满前都是无阻塞的向里面填入内容， 且当队列非空时消费者可以无阻塞的从channel中接收通知，队列空时消费者阻塞。 而无缓冲的channel提供的是阻塞的通知机制，生产者在向其中填入内容时可能会阻塞， 阻塞的条件是消费者此时是否已经阻塞在channel上等待接收，如果没有则生产者阻塞。</summary></entry><entry><title type="html">对比Go和C++编译器生成的汇编</title><link href="/programming/2022/11/09/govscpp.html" rel="alternate" type="text/html" title="对比Go和C++编译器生成的汇编" /><published>2022-11-09T00:00:00+00:00</published><updated>2022-11-09T00:00:00+00:00</updated><id>/programming/2022/11/09/govscpp</id><content type="html" xml:base="/programming/2022/11/09/govscpp.html">&lt;p&gt;写代码的时候发现一个有趣的现象，同样的一段循环的逻辑，使用Go语言竟然比C++快不少。
十分简单的代码却有不同的运行速度，怀疑是两个编译器生成了不同的汇编，于是进行深入调查。&lt;/p&gt;

&lt;h2 id=&quot;1-go版本&quot;&gt;1. Go版本&lt;/h2&gt;

&lt;p&gt;go version go1.19.2 linux/amd64&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1000000000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;编译运行&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;go build
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;time&lt;/span&gt; ./app
real    0m0.168s
user    0m0.168s
sys     0m0.000s
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;2-c版本&quot;&gt;2. C++版本&lt;/h2&gt;

&lt;p&gt;g++ (Ubuntu 11.2.0-19ubuntu1) 11.2.0&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1'000'000'000ull&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;编译运行&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;g++ main.cpp
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;time&lt;/span&gt; ./a.out
real    0m0.331s
user    0m0.331s
sys     0m0.000s
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;3-分析一&quot;&gt;3. 分析一&lt;/h2&gt;

&lt;p&gt;上述代码的运行环境是Win11 22H2 WSL2，Ubuntu 22.04，CPU是i7-12700H，内存32GB。&lt;/p&gt;

&lt;p&gt;直接上objdump，分析上面生成的两个elf文件。
需要指出的是，我在编译C++代码时没有用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-O&lt;/code&gt;选项。
这种简单的循环在开了g++优化选项后会被编译器当作无副作用代码而舍弃掉。&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;objdump &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; ./app &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; app.asm
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;objdump &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; ./a.out &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; main.asm
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;还原汇编后，把代码拉到最下面，就可以看到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main&lt;/code&gt;函数真正的样子。&lt;/p&gt;

&lt;p&gt;首先是Go编译器生成的汇编。大致意思是，将&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rax&lt;/code&gt;寄存器置0（低32位的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eax&lt;/code&gt;对自身取异或即可实现），然后反复对&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rax&lt;/code&gt;里面的值加1（&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inc&lt;/code&gt;指令）并且判断这个值是否满足条件（第一次运行直接跳转到457c07行，随后在457c04到457c0d之间循环，直到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cmp&lt;/code&gt;的结果不会让&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jl&lt;/code&gt;跳转，程序将执行&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ret&lt;/code&gt;退出函数）。这里go编译器把原先代码里的变量&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;i&lt;/code&gt;映射为了寄存器中的一个值。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;0000000000457c00 &amp;lt;main.main&amp;gt;:
  457c00:	31 c0                	xor    %eax,%eax
  457c02:	eb 03                	jmp    457c07 &amp;lt;main.main+0x7&amp;gt;
  457c04:	48 ff c0             	inc    %rax
  457c07:	48 3d 00 ca 9a 3b    	cmp    $0x3b9aca00,%rax
  457c0d:	7c f5                	jl     457c04 &amp;lt;main.main+0x4&amp;gt;
  457c0f:	c3                   	ret
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;其次是g++生成的汇编。可以看到，C++代码中的变量&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;i&lt;/code&gt;作为一个函数局部变量，被放到了当前的栈空间上，用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-0x4(%rbp)&lt;/code&gt;表示其位置。其余的逻辑，就和Go的汇编一样了。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;0000000000001129 &amp;lt;main&amp;gt;:
    1129:	f3 0f 1e fa          	endbr64
    112d:	55                   	push   %rbp
    112e:	48 89 e5             	mov    %rsp,%rbp
    1131:	c7 45 fc 00 00 00 00 	movl   $0x0,-0x4(%rbp)
    1138:	eb 04                	jmp    113e &amp;lt;main+0x15&amp;gt;
    113a:	83 45 fc 01          	addl   $0x1,-0x4(%rbp)
    113e:	81 7d fc ff c9 9a 3b 	cmpl   $0x3b9ac9ff,-0x4(%rbp)
    1145:	7e f3                	jle    113a &amp;lt;main+0x11&amp;gt;
    1147:	b8 00 00 00 00       	mov    $0x0,%eax
    114c:	5d                   	pop    %rbp
    114d:	c3                   	ret
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;那么，是不是就可以判断上述C++代码比Go代码慢一倍的原因，在于g++编译的时候没有对变量位置进行寄存器优化呢？如果猜想成立，也就意味着当使用g++寄存器优化后，两份代码的运行时间应该相近。&lt;/p&gt;

&lt;h2 id=&quot;4-分析二&quot;&gt;4. 分析二&lt;/h2&gt;

&lt;p&gt;根据上面的分析，我们需要调整编译参数或者使用代码上的技巧，来使C++编译器把变量放在寄存器中而不是内存里（cache）。不过，我并没有找到相关的优化技巧。反而是C语言支持&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;register&lt;/code&gt;关键字可以指导编译器对变量进行寄存器优化。那么让我们先来用C语言试一试。&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;register&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1000000000ull&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;编译运行&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;gcc main.c
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;time&lt;/span&gt; ./a.out
real    0m0.237s
user    0m0.237s
sys     0m0.000s
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;的确快了，但也没有特别快，而且多次运行会出现比较大的波动，在190ms到290ms之间。看一看汇编长什么样。核心部分，将&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rbx&lt;/code&gt;寄存器置0，并且反复加1（&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;add&lt;/code&gt;指令）。但是，比较的时候却是先把&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rbx&lt;/code&gt;寄存器里面的值拷贝到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rax&lt;/code&gt;中，然后判断&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rax&lt;/code&gt;是否满足条件（&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cmp&lt;/code&gt;指令）。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;0000000000001129 &amp;lt;main&amp;gt;:
    1129:	f3 0f 1e fa          	endbr64
    112d:	55                   	push   %rbp
    112e:	48 89 e5             	mov    %rsp,%rbp
    1131:	53                   	push   %rbx
    1132:	bb 00 00 00 00       	mov    $0x0,%ebx
    1137:	eb 04                	jmp    113d &amp;lt;main+0x14&amp;gt;
    1139:	48 83 c3 01          	add    $0x1,%rbx
    113d:	48 89 d8             	mov    %rbx,%rax
    1140:	48 3d ff c9 9a 3b    	cmp    $0x3b9ac9ff,%rax
    1146:	76 f1                	jbe    1139 &amp;lt;main+0x10&amp;gt;
    1148:	b8 00 00 00 00       	mov    $0x0,%eax
    114d:	48 8b 5d f8          	mov    -0x8(%rbp),%rbx
    1151:	c9                   	leave
    1152:	c3                   	ret
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里每次循环都要多进行一次寄存器拷贝，也挺迷惑的。也许把这个多余的拷贝去掉速度就可以再次提升。这时，单纯写C或C++代码已经不容易引导编译器往我们期望的方向上去做了，也许只有直接写汇编能拯救这段代码了。关于汇编的问题，其实也有很多说法，也就是到底哪些部分需要用汇编替代才能实现更高效的for循环。比如如下代码，其实并不能很好的提升性能。&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;register&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1000000000ull&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;asm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;add $1, %0&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;+r&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;关于怎么用gcc内联汇编去提升上面那段代码的性能，以后再写吧。&lt;/p&gt;

&lt;h2 id=&quot;5-总结&quot;&gt;5. 总结&lt;/h2&gt;

&lt;p&gt;写最初的那段代码，本来是想用紧循环做出一个微秒级任务，结果发现了不同语言的编译器对于紧循环在处理上的差异。
当然，实际做开发时不太可能出现这种无用的紧循环代码，所以使用C/C++编译器时打开-O通常来讲不会有错。
但如果某个线程真的需要短时间的原地等待，比如大约1微妙的等待，那么C/C++应该有相应的写法，
简单的不加优化选项会导致频繁的缓存访问，不见得优雅。
要用C/C++写出和Go等效的循环，这个问题以后再研究。&lt;/p&gt;</content><author><name></name></author><category term="programming" /><summary type="html">写代码的时候发现一个有趣的现象，同样的一段循环的逻辑，使用Go语言竟然比C++快不少。 十分简单的代码却有不同的运行速度，怀疑是两个编译器生成了不同的汇编，于是进行深入调查。 1. Go版本 go version go1.19.2 linux/amd64 package main func main() { for i := 0; i &amp;lt; 1000000000; i++ {} } 编译运行 $ go build $ time ./app real 0m0.168s user 0m0.168s sys 0m0.000s 2. C++版本 g++ (Ubuntu 11.2.0-19ubuntu1) 11.2.0 int main() { for (long long i = 0; i &amp;lt; 1'000'000'000ull; i++) {} } 编译运行 $ g++ main.cpp $ time ./a.out real 0m0.331s user 0m0.331s sys 0m0.000s 3. 分析一 上述代码的运行环境是Win11 22H2 WSL2，Ubuntu 22.04，CPU是i7-12700H，内存32GB。 直接上objdump，分析上面生成的两个elf文件。 需要指出的是，我在编译C++代码时没有用-O选项。 这种简单的循环在开了g++优化选项后会被编译器当作无副作用代码而舍弃掉。 $ objdump -d ./app &amp;gt; app.asm $ objdump -d ./a.out &amp;gt; main.asm 还原汇编后，把代码拉到最下面，就可以看到main函数真正的样子。 首先是Go编译器生成的汇编。大致意思是，将rax寄存器置0（低32位的eax对自身取异或即可实现），然后反复对rax里面的值加1（inc指令）并且判断这个值是否满足条件（第一次运行直接跳转到457c07行，随后在457c04到457c0d之间循环，直到cmp的结果不会让jl跳转，程序将执行ret退出函数）。这里go编译器把原先代码里的变量i映射为了寄存器中的一个值。 0000000000457c00 &amp;lt;main.main&amp;gt;: 457c00: 31 c0 xor %eax,%eax 457c02: eb 03 jmp 457c07 &amp;lt;main.main+0x7&amp;gt; 457c04: 48 ff c0 inc %rax 457c07: 48 3d 00 ca 9a 3b cmp $0x3b9aca00,%rax 457c0d: 7c f5 jl 457c04 &amp;lt;main.main+0x4&amp;gt; 457c0f: c3 ret 其次是g++生成的汇编。可以看到，C++代码中的变量i作为一个函数局部变量，被放到了当前的栈空间上，用-0x4(%rbp)表示其位置。其余的逻辑，就和Go的汇编一样了。 0000000000001129 &amp;lt;main&amp;gt;: 1129: f3 0f 1e fa endbr64 112d: 55 push %rbp 112e: 48 89 e5 mov %rsp,%rbp 1131: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp) 1138: eb 04 jmp 113e &amp;lt;main+0x15&amp;gt; 113a: 83 45 fc 01 addl $0x1,-0x4(%rbp) 113e: 81 7d fc ff c9 9a 3b cmpl $0x3b9ac9ff,-0x4(%rbp) 1145: 7e f3 jle 113a &amp;lt;main+0x11&amp;gt; 1147: b8 00 00 00 00 mov $0x0,%eax 114c: 5d pop %rbp 114d: c3 ret 那么，是不是就可以判断上述C++代码比Go代码慢一倍的原因，在于g++编译的时候没有对变量位置进行寄存器优化呢？如果猜想成立，也就意味着当使用g++寄存器优化后，两份代码的运行时间应该相近。 4. 分析二 根据上面的分析，我们需要调整编译参数或者使用代码上的技巧，来使C++编译器把变量放在寄存器中而不是内存里（cache）。不过，我并没有找到相关的优化技巧。反而是C语言支持register关键字可以指导编译器对变量进行寄存器优化。那么让我们先来用C语言试一试。 int main() { for (register int i = 0; i &amp;lt; 1000000000ull; i++) {} } 编译运行 $ gcc main.c $ time ./a.out real 0m0.237s user 0m0.237s sys 0m0.000s 的确快了，但也没有特别快，而且多次运行会出现比较大的波动，在190ms到290ms之间。看一看汇编长什么样。核心部分，将rbx寄存器置0，并且反复加1（add指令）。但是，比较的时候却是先把rbx寄存器里面的值拷贝到rax中，然后判断rax是否满足条件（cmp指令）。 0000000000001129 &amp;lt;main&amp;gt;: 1129: f3 0f 1e fa endbr64 112d: 55 push %rbp 112e: 48 89 e5 mov %rsp,%rbp 1131: 53 push %rbx 1132: bb 00 00 00 00 mov $0x0,%ebx 1137: eb 04 jmp 113d &amp;lt;main+0x14&amp;gt; 1139: 48 83 c3 01 add $0x1,%rbx 113d: 48 89 d8 mov %rbx,%rax 1140: 48 3d ff c9 9a 3b cmp $0x3b9ac9ff,%rax 1146: 76 f1 jbe 1139 &amp;lt;main+0x10&amp;gt; 1148: b8 00 00 00 00 mov $0x0,%eax 114d: 48 8b 5d f8 mov -0x8(%rbp),%rbx 1151: c9 leave 1152: c3 ret 这里每次循环都要多进行一次寄存器拷贝，也挺迷惑的。也许把这个多余的拷贝去掉速度就可以再次提升。这时，单纯写C或C++代码已经不容易引导编译器往我们期望的方向上去做了，也许只有直接写汇编能拯救这段代码了。关于汇编的问题，其实也有很多说法，也就是到底哪些部分需要用汇编替代才能实现更高效的for循环。比如如下代码，其实并不能很好的提升性能。 int main() { register int i = 0; while (i &amp;lt; 1000000000ull) { asm(&quot;add $1, %0&quot; : &quot;+r&quot;(i)); } return 0; } 关于怎么用gcc内联汇编去提升上面那段代码的性能，以后再写吧。 5. 总结 写最初的那段代码，本来是想用紧循环做出一个微秒级任务，结果发现了不同语言的编译器对于紧循环在处理上的差异。 当然，实际做开发时不太可能出现这种无用的紧循环代码，所以使用C/C++编译器时打开-O通常来讲不会有错。 但如果某个线程真的需要短时间的原地等待，比如大约1微妙的等待，那么C/C++应该有相应的写法， 简单的不加优化选项会导致频繁的缓存访问，不见得优雅。 要用C/C++写出和Go等效的循环，这个问题以后再研究。</summary></entry></feed>