false sharing我们一般说的是多核的问题,这个问题主要出现在CPU的缓存上,我们知道CPU有多级缓存,而CPU缓存单单位是行(主流一个缓存行是64Byte, 也就是8个int64)
CPU加载缓存
当我们要操作一个变量A时会把A附近的64个字节都加载到缓存行中(空间局部性原理),这样在CPU的缓存里操作时要比在内存中要快
多核CPU缓存问题
在多核心中每个CPU核心都有自己的缓存,如果A和B变量是挨着的, 当CPU1要写A变量, CPU2读变量B, 因为AB挨着,CPU1和CPU2都把它们加载到自己的缓存中,并且AB在同一个缓存行中,CPU1改了A导致了CPU2中B缓存失效,CPUB就得重新从内存中加载缓存
这种情况就是"假共享"/“伪共享”/“false sharing”
说白了就是,CPU各自都有一份,因为邻近变量修改导致了其他核心缓存失效
例子
CASE1
type FS struct {
X int64
Y int64
}
func share() {
var a FS
wg := sync.WaitGroup{}
wg.Add(2)
start := time.Now()
go func() {
for i := 0; i < 100000000; i++ {
a.X++
}
wg.Done()
}()
go func() {
for i := 0; i < 100000000; i++ {
a.Y++
}
wg.Done()
}()
wg.Wait()
fmt.Println(time.Since(start))
}
这种就创造了两个变量挨着在同一缓存行的情况,这个代码在我本机运行时间为305.462012ms
CASE 2
我们把上面结构体改一下,X和Y间隔56个字节, 使他们不在一个缓存行, 其他部分不变
type FS struct {
X int64
_ [7]int64
Y int64
}
本地运行时间变为201.209402ms, 节省了100ms
注意区分false sharing
和data race
- false sharing讲的是多个核心数据共享缓存失效的问题, 只是影响程序性能,并不影响程序正确性(解决这个问题可以使用内存填充,就像CASE2那样)
- data race讲的是并发访问同一个变量问题, 会导致意外的情况, 程序不能正确执行(没有安全的数据竞争,发生数据竞争要使用同步原语解决)