为什么Go语言不显示内存重排序?

huangapple go评论123阅读模式
英文:

Why Go doesn't show memory-reordering?

问题

我正在阅读preshing的博客《Memory Reordering Caught in the Act》,并通过他的示例代码ordering.zip复现了内存重排序。

然后我想知道是否可以通过Go语言来复现内存重排序,所以我用Go语言编写了示例代码,但是在Go语言中没有显示出内存重排序。

我写信是为了分享一些发现。

你能帮忙解释一下为什么Go语言无法实现内存重排序吗?谢谢。

Go语言示例代码:

package main

import (
	"fmt"
	"math/rand"
)

var x, y, r1, r2 int
var detected = 0

func randWait() {
	for rand.Intn(8) != 0 {
	}
}

func main() {
	beginSig1 := make(chan bool, 1)
	beginSig2 := make(chan bool, 1)
	endSig1 := make(chan bool, 1)
	endSig2 := make(chan bool, 1)
	go func() {
		for {
			<-beginSig1
			randWait()
			x = 1
			r1 = y
			endSig1 <- true
		}
	}()
	go func() {
		for {
			<-beginSig2
			randWait()
			y = 1
			r2 = x
			endSig2 <- true
		}
	}()
	for i := 1; ; i = i + 1 {
		x = 0
		y = 0
		beginSig1 <- true
		beginSig2 <- true
		<-endSig1
		<-endSig2
		if r1 == 0 && r2 == 0 {
			detected = detected + 1
			fmt.Println(detected, "reorders detected after ", i, "iterations")
		}
	}
}

通过使用"ndisasm -b 32"命令生成的汇编代码显示了C++和Go之间的差异。

  • C++的汇编代码
00000CF0  C705520300000100  mov dword [0x352],0x1     //X=1
         -0000
00000CFA  8B0550030000      mov eax,[0x350]     
00000D00  89054E030000      mov [0x34e],eax      //r1=Y
  • Go的汇编代码
000013EA  48                dec eax
000013EB  C70425787F170001  mov dword [0x177f78],0x1     //x=1
         -000000
000013F6  48                dec eax
000013F7  8B1C25807F1700    mov ebx,[0x177f80]
000013FE  48                dec eax
000013FF  891C25687F1700    mov [0x177f68],ebx          //r1=Y
00001406  48                dec eax

看起来Go语言在访问共享内存时使用了dec eax指令,但是dec eax指令不能防止内存重排序。

  1. 《Intel® 64 and IA-32 Architectures Software Developer Manuals》第3卷第8.2节显示了可以防止内存重排序的情况,但是dec eax指令并不包括在内...

  2. 我尝试在C代码中添加dec eax作为共享内存访问的边界,但是内存重排序仍然存在。

到目前为止,我对原因一无所知。请帮助我解决这个问题,谢谢。

英文:

I'm reading preshing's blog Memory Reordering Caught in the Act, and reproduced memory-reordering by his example code

Then I wonder if I could reproduce memory-reordering by Go, so I wrote the example code in go, but memory-reordering is not shown in Go.

I'm writing to share some findings.

And could you help explain why Go could not get memory-reordering? Thanks.

Example code in Go:

 package main
    
    import (
            &quot;fmt&quot;
            &quot;math/rand&quot;
    )
    
    var x, y, r1, r2 int
    var detected = 0
    
    func randWait() {
            for rand.Intn(8) != 0 {
            }
    }
    
    func main() {
            beginSig1 := make(chan bool, 1)
            beginSig2 := make(chan bool, 1)
            endSig1 := make(chan bool, 1)
            endSig2 := make(chan bool, 1)
            go func() {
                    for {
                            &lt;-beginSig1
                            randWait()
                            x = 1
                            r1 = y
                            endSig1 &lt;- true
                    }
            }()
            go func() {
                    for {
                            &lt;-beginSig2
                            randWait()
                            y = 1
                            r2 = x
                            endSig2 &lt;- true
                    }
            }()
            for i := 1; ; i = i + 1 {
                    x = 0
                    y = 0
                    beginSig1 &lt;- true
                    beginSig2 &lt;- true
                    &lt;-endSig1
                    &lt;-endSig2
                    if r1 == 0 &amp;&amp; r2 == 0 {
                            detected = detected + 1
                            fmt.Println(detected, &quot;reorders detected after &quot;, i, &quot;iterations&quot;)
                    }
            }
    } 

The assembly code (by "ndisasm -b 32") show different between C++ vs Go

  • Assembly code from C++

      00000CF0  C705520300000100  mov dword [0x352],0x1     //X=1
               -0000
      00000CFA  8B0550030000      mov eax,[0x350]     
      00000D00  89054E030000      mov [0x34e],eax      //r1=Y
    
  • Assembly code from Go

      000013EA  48                dec eax
      000013EB  C70425787F170001  mov dword [0x177f78],0x1     //x=1
               -000000
      000013F6  48                dec eax
      000013F7  8B1C25807F1700    mov ebx,[0x177f80]
      000013FE  48                dec eax
      000013FF  891C25687F1700    mov [0x177f68],ebx          //r1=Y
      00001406  48                dec eax
    

it seems Go uses dec eax around access of shared memory, but it make no sense that dec eax can prevent memory-reordering

  1. The Intel® 64 and IA-32 Architectures Software Developer Manuals Volume 3, Section 8.2 shows the cases that could prevent memory-reordering, but dec eax isn't included...

  2. I tried to add dec eax as margin of shared-memory-access in C code, and memory-reordering is still there.

By now, I have no clue about the reason. Please help me on it, thank you.

答案1

得分: 8

我在代码中没有看到调用GOMAXPROCs的地方。如果你没有调用它,那么程序将只在一个CPU上运行,这样就不会显示重排序。在Go 1.5(发布于2015/08/19)及以后的版本中,你不再需要设置GOMAXPROCs,Go会默认使用所有的CPU。

英文:

I don't see a call to set GOMAXPROCs anywhere? If you don't call it you'll be running on just one CPU which won't ever show re-ordering: http://golang.org/pkg/runtime/#GOMAXPROCS

Update: In Go 1.5 (Released 2015/08/19) and later you no longer need to set GOMAXPROCS - Go defaults to using all your CPUs.

答案2

得分: 0

Go的内存模型与C或C++的内存模型不同。

请参考http://golang.org/ref/mem,该链接描述了“Happens Before”(HB)关系以及它与通道的关系。请注意,当前的实现可能比内存模型所要求的HB关系更多。

英文:

The Go memory model is not that of C or C++.

Have a look at http://golang.org/ref/mem which describes the "Happens Before" HB relation and how it relates to channels. Note that the current implementations may have more HB than required by the memory model.

答案3

得分: 0

我相信mov ebx,[0x177f80]指令起到了关键作用。

它加载了ebx,这意味着mov [0x177f68],ebx依赖于它,不能在它之前移动。因此,如果存在重新排序,使用ebx的这两个移动指令必须一起重新排序。我认为这是不允许的 - x86_64架构不会将读取与其他读取重新排序(对此不是100%确定)。

英文:

I believe the mov ebx,[0x177f80] instruction makes the difference.

It loads ebx, which means mov [0x177f68],ebx is dependent on it, and can't be moved ahead of it. So if there's reordering, both moves that use ebx must be reordered together. I think this is not allowed - the x86_64 architecutre doesn't reorder reads with other reads (not 100% sure on this).

huangapple
  • 本文由 发表于 2013年11月11日 16:12:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/19901615.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定