英文:
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
指令不能防止内存重排序。
-
《Intel® 64 and IA-32 Architectures Software Developer Manuals》第3卷第8.2节显示了可以防止内存重排序的情况,但是
dec eax
指令并不包括在内... -
我尝试在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 (
"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")
}
}
}
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
-
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... -
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).
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论