← 中文资源

简体中文版本

伪共享

伪共享发生在不同核心使用的独立变量落在同一缓存行时:逻辑上毫无共享,核心间却因一致性流量持续互相失效。

阅读英文原文

概念定义

缓存一致性按行(通常 64 字节)运作,而非按变量。两个线程写同一行内的不同变量时,每次写都让另一核的副本失效。变量逻辑私有、物理共享,性能表现出争用般的数倍劣化——且只有剖析才看得见。

工作机制

每次写入,核心都要独占该行并使他核副本失效;上一个写者下次再写又要重新取回,行就这样乒乓。检测靠聚集在同一地址段的缓存未命中计数,或 perf c2c 高亮被多线程命中的行。修复是填充或重排,让每个热写者独占整行。

使用场景

每线程统计结构体、环形缓冲头部、任务队列元数据,以及“每个 worker 一个字段且连续排布”的任何结构。把热可变字段按行对齐,或按写者而非按类别分组。

常见误区

“只有共享数据才有争用”错在单位:一致性按行不按变量。线程越多,行上的失效流量越倍增。盲目填充会让冷数据白白吃内存与缓存密度——先测量。GC 语言布局不受控:改用带填充槽的结构数组或堆外结构。一次无害的重构把两个热字段并进同一结构体,伪共享就会悄悄现身。

代码示例

package main

import (
	"fmt"
	"sync"
	"sync/atomic"
	"time"
)

type Counters struct {
	requests atomic.Int64
	errors   atomic.Int64
	bytes    atomic.Int64
}

func (c *Counters) IncRequest()   { c.requests.Add(1) }
func (c *Counters) IncError()     { c.errors.Add(1) }
func (c *Counters) AddBytes(n int64) { c.bytes.Add(n) }

func (c *Counters) Snapshot() (req, errs, transferred int64) {
	return c.requests.Load(), c.errors.Load(), c.bytes.Load()
}

type RateCalculator struct {
	lastValue atomic.Int64
	lastTime  atomic.Int64
}

func (r *RateCalculator) Rate(now time.Time, current int64) float64 {
	prevTime := r.lastTime.Load()
	prevValue := r.lastValue.Load()

	if prevTime == 0 {
		r.lastTime.Store(now.Unix())
		r.lastValue.Store(current)
		return 0
	}

	elapsed := now.Unix() - prevTime
	if elapsed <= 0 {
		return 0
	}
	rate := float64(current-prevValue) / float64(elapsed)

	r.lastTime.Store(now.Unix())
	r.lastValue.Store(current)
	return rate
}

func main() {
	counters := new(Counters)
	rate := new(RateCalculator)

	var wg sync.WaitGroup
	for i := 0; i < 100; i++ {
		wg.Add(1)
		go func(n int) {
			defer wg.Done()
			counters.IncRequest()
			if n%50 == 0 {
				counters.IncError() // 模拟约百分之二的错误率
			}
			counters.AddBytes(512)
		}(i)
	}
	wg.Wait()

	req, errs, transferred := counters.Snapshot()
	fmt.Println(req, errs, transferred, rate.Rate(time.Now(), req))
}
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"sync"
)

var bufferPool = sync.Pool{
	New: func() any { return new(bytes.Buffer) },
}

func EncodeJSON(value any) ([]byte, error) {
	buffer := bufferPool.Get().(*bytes.Buffer)
	buffer.Reset()
	defer bufferPool.Put(buffer)

	if err := json.NewEncoder(buffer).Encode(value); err != nil {
		return nil, err
	}
	out := make([]byte, buffer.Len())
	copy(out, buffer.Bytes())
	return out, nil
}

func main() {
	type payload struct {
		ID   int    `json:"id"`
		Name string `json:"name"`
	}
	encoded, err := EncodeJSON(payload{ID: 1, Name: "Ada"})
	if err != nil {
		panic(err)
	}
	fmt.Print(string(encoded))
	fmt.Println(bytes.Equal(encoded, []byte("{\"id\":1,\"name\":\"Ada\"}\n")))
}