[譯] 什麼是緩存 false sharing 以及如何解決(Golang 示例)

在解釋緩存 false sharing 以前,有必要簡要介紹一下緩存在 CPU 架構中的工做原理。git

CPU 中緩存的最小化單位是緩存行(如今來講,CPU 中常見的緩存行大小爲 64 字節)。所以,當 CPU 從內存中讀取變量時,它將讀取該變量附近的全部變量。圖 1 是一個簡單的例子:github

圖1

當 core1 從內存中讀取變量 a 時,它會同時將變量 b 讀入緩存。(順便說一下,我認爲 CPU 從內存中批量讀取變量的主要緣由是基於空間局部性理論:當 CPU 訪問一個變量時,它可能很快就會讀取它旁邊的變量。)(譯者注:關於空間局部性理論能夠參考這篇文章golang

該緩存架構存在一個問題:若是一個變量存在於不一樣 CPU 核心中的兩個緩存行中,如圖 2 所示:shell

圖2

當 core1 更新變量 a 時:緩存

圖3

當 core2 讀取變量 b 時,即便變量 b 未被修改,它也會使 core2 的緩存未命中。因此 core2 會從內存中從新加載緩存行中的全部變量,如圖 4 所示:bash

圖4

這就是緩存 false sharing:一個 CPU 核更新變量會強制其餘 CPU 核更新緩存。而咱們都知道從緩存中讀取 CPU 的變量比從內存中讀取變量要快得多。所以,雖然該變量一直存在於多核中,但這會顯著影響性能。架構

解決該問題的經常使用方法是緩存填充:在變量之間填充一些無心義的變量。使一個變量單獨佔用 CPU 核的緩存行,所以當其餘核更新時,其餘變量不會使該核從內存中從新加載變量。性能

咱們使用以下的 Go 代碼來簡要介紹緩存 false sharing 的概念。測試

這是一個帶有三個 uint64 變量的結構體,ui

type NoPad struct {
	a uint64
	b uint64
	c uint64
}

func (myatomic *NoPad) IncreaseAllEles() {
	atomic.AddUint64(&myatomic.a, 1)
	atomic.AddUint64(&myatomic.b, 1)
	atomic.AddUint64(&myatomic.c, 1)
}
複製代碼

這是另外一個結構,我使用 [8]uint64 來作緩存填充:

type Pad struct {
	a   uint64
	_p1 [8]uint64
	b   uint64
	_p2 [8]uint64
	c   uint64
	_p3 [8]uint64
}

func (myatomic *Pad) IncreaseAllEles() {
	atomic.AddUint64(&myatomic.a, 1)
	atomic.AddUint64(&myatomic.b, 1)
	atomic.AddUint64(&myatomic.c, 1)
}
複製代碼

而後寫一個簡單的代碼來運行基準測試:

func testAtomicIncrease(myatomic MyAtomic) {
	paraNum := 1000
	addTimes := 1000
	var wg sync.WaitGroup
	wg.Add(paraNum)
	for i := 0; i < paraNum; i++ {
		go func() {
			for j := 0; j < addTimes; j++ {
				myatomic.IncreaseAllEles()
			}
			wg.Done()
		}()
	}
	wg.Wait()

}
func BenchmarkNoPad(b *testing.B) {
	myatomic := &NoPad{}
	b.ResetTimer()
	testAtomicIncrease(myatomic)
}

func BenchmarkPad(b *testing.B) {
	myatomic := &Pad{}
	b.ResetTimer()
	testAtomicIncrease(myatomic)
}
複製代碼

使用 2014 年的 MacBook Air 作的基準測試結果以下:

$> go test -bench=.
BenchmarkNoPad-4 2000000000 0.07 ns/op
BenchmarkPad-4 2000000000 0.02 ns/op
PASS
ok 1.777s
複製代碼

基準測試的結果代表它將性能從 0.07 ns/op 提升到了 0.02 ns/op,這是一個很大的提升。

你也能夠用其餘語言測試這個,好比 Java,我相信你會獲得相同的結果。

在將其應用於你的代碼以前,應該瞭解兩個要點:

  1. 確保系統中 CPU 的緩存行大小:這與你使用的緩存填充大小有關。
  2. 填充更多變量意味着消耗更多內存資源。在你的方案中運行基準測試以確保這些內存消耗是值得的。

個人全部示例代碼都在GitHub上。

相關文章
相關標籤/搜索