1. 性能测试方案设计
- 定义测试结构体
- 定义原生
map
及其操作函数。
- 定义使用读写锁的自定义
map
结构体及方法,在读写操作时加锁。
- 使用
sync.Map
及其原生方法。
- 定义分段锁实现的
map
结构体及方法,将 map
分成多个段,每个段有独立的锁。
// 原生map
var nativeMap = make(map[string]int)
// 带读写锁的map
type RWMutexMap struct {
mu sync.RWMutex
data map[string]int
}
func (m *RWMutexMap) Get(key string) int {
m.mu.RLock()
defer m.mu.RUnlock()
return m.data[key]
}
func (m *RWMutexMap) Set(key string, value int) {
m.mu.Lock()
defer m.mu.Unlock()
m.data[key] = value
}
// sync.Map
var syncMap sync.Map
// 分段锁map
const segmentCount = 16
type Segment struct {
mu sync.RWMutex
data map[string]int
}
type ShardedMap struct {
segments [segmentCount]Segment
}
func (m *ShardedMap) Get(key string) int {
hash := int(fnv32(key)) % segmentCount
m.segments[hash].mu.RLock()
defer m.segments[hash].mu.RUnlock()
return m.segments[hash].data[key]
}
func (m *ShardedMap) Set(key string, value int) {
hash := int(fnv32(key)) % segmentCount
m.segments[hash].mu.Lock()
defer m.segments[hash].mu.Unlock()
m.segments[hash].data[key] = value
}
func fnv32(key string) uint32 {
const offsetBasis = 2166136261
const prime = 16777619
hash := offsetBasis
for _, b := range key {
hash = hash ^ uint32(b)
hash = hash * prime
}
return hash
}
- 性能测试函数
- 编写不同并发场景的测试函数,使用
sync.WaitGroup
控制并发操作。
func benchmarkNativeMap(b *testing.B, readRatio float64) {
for n := 0; n < b.N; n++ {
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
if rand.Float64() < readRatio {
_ = nativeMap["key"]
} else {
nativeMap["key"] = 1
}
}()
}
wg.Wait()
}
}
func benchmarkRWMutexMap(b *testing.B, readRatio float64) {
m := &RWMutexMap{data: make(map[string]int)}
for n := 0; n < b.N; n++ {
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
if rand.Float64() < readRatio {
_ = m.Get("key")
} else {
m.Set("key", 1)
}
}()
}
wg.Wait()
}
}
func benchmarkSyncMap(b *testing.B, readRatio float64) {
for n := 0; n < b.N; n++ {
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
if rand.Float64() < readRatio {
_, _ = syncMap.Load("key")
} else {
syncMap.Store("key", 1)
}
}()
}
wg.Wait()
}
}
func benchmarkShardedMap(b *testing.B, readRatio float64) {
m := &ShardedMap{}
for n := 0; n < b.N; n++ {
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
if rand.Float64() < readRatio {
_ = m.Get("key")
} else {
m.Set("key", 1)
}
}()
}
wg.Wait()
}
}
- 运行测试
- 使用
testing
包编写测试用例,针对不同并发场景(高读低写 readRatio = 0.9
、高写低读 readRatio = 0.1
、读写均衡 readRatio = 0.5
)分别运行上述测试函数。
func BenchmarkNativeMapHighRead(b *testing.B) {
benchmarkNativeMap(b, 0.9)
}
func BenchmarkNativeMapHighWrite(b *testing.B) {
benchmarkNativeMap(b, 0.1)
}
func BenchmarkNativeMapBalanced(b *testing.B) {
benchmarkNativeMap(b, 0.5)
}
func BenchmarkRWMutexMapHighRead(b *testing.B) {
benchmarkRWMutexMap(b, 0.9)
}
func BenchmarkRWMutexMapHighWrite(b *testing.B) {
benchmarkRWMutexMap(b, 0.1)
}
func BenchmarkRWMutexMapBalanced(b *testing.B) {
benchmarkRWMutexMap(b, 0.5)
}
func BenchmarkSyncMapHighRead(b *testing.B) {
benchmarkSyncMap(b, 0.9)
}
func BenchmarkSyncMapHighWrite(b *testing.B) {
benchmarkSyncMap(b, 0.1)
}
func BenchmarkSyncMapBalanced(b *testing.B) {
benchmarkSyncMap(b, 0.5)
}
func BenchmarkShardedMapHighRead(b *testing.B) {
benchmarkShardedMap(b, 0.9)
}
func BenchmarkShardedMapHighWrite(b *testing.B) {
benchmarkShardedMap(b, 0.1)
}
func BenchmarkShardedMapBalanced(b *testing.B) {
benchmarkShardedMap(b, 0.5)
}
2. 测试结果分析
- 高读低写场景
- 原生
map
:没有锁机制,读操作理论上最快,但并发写时可能出现数据竞争,实际测试中性能会因数据竞争而不稳定。
- 读写锁
map
:读操作加读锁,在高读场景下性能较好,但写操作加写锁会阻塞读操作,可能影响整体性能。
sync.Map
:在高读场景下表现良好,内部采用了无锁数据结构优化读操作。
- 分段锁
map
:读操作只需获取对应段的锁,在高读场景下性能也不错,比读写锁 map
有一定优势,因为锁的粒度更小。
- 选择:如果追求极致读性能且对数据一致性要求不高,原生
map
可能有优势;如果对数据一致性要求严格,sync.Map
或分段锁 map
更合适,sync.Map
实现简单,分段锁 map
可根据业务场景调整分段数进一步优化。
- 高写低读场景
- 原生
map
:数据竞争问题严重,性能差。
- 读写锁
map
:写操作加锁会阻塞读操作,整体性能受影响。
sync.Map
:写操作性能相对较好,内部有优化机制。
- 分段锁
map
:由于锁粒度小,写操作时其他段仍可读写,性能比读写锁 map
好。
- 选择:
sync.Map
和分段锁 map
更适合高写低读场景,分段锁 map
在高并发写且数据量较大时,通过合理分段能进一步提升性能。
- 读写均衡场景
- 原生
map
:数据竞争导致性能不稳定。
- 读写锁
map
:读写操作相互影响,性能一般。
sync.Map
:综合性能较好,能适应读写均衡场景。
- 分段锁
map
:通过合理分段,能较好地平衡读写操作,性能也不错。
- 选择:
sync.Map
和分段锁 map
是较好选择,具体取决于应用场景对锁粒度控制和实现复杂度的要求,分段锁 map
更灵活可定制,sync.Map
更简洁通用。