是否可以LoadOrStore进入 Go 而sync.Map无需每次都创建新的结构?如果没有,有哪些替代方案?
这里的用例是,如果我使用作为sync.Map缓存,其中缓存未命中很少(但可能),并且在缓存未命中时我想添加到映射中,我需要在每次LoadOrStore调用时初始化一个结构,而不仅仅是在需要时创建结构。我担心这会损害 GC,初始化数十万个不需要的结构。
在 Java 中,这可以使用computeIfAbsent.
pet*_*rSO -1
Run Code Online (Sandbox Code Playgroud)import "sync"Map 类似于 Go 的 map[interface{}]interface{},但可以安全地由多个 goroutine 并发使用,无需额外的锁定或协调。加载、存储和删除在分摊常量时间内运行。
Map 类型是专门的。大多数代码应该使用普通的 Go 映射,并具有单独的锁定或协调,以获得更好的类型安全性,并更容易维护其他不变量以及映射内容。
Map 类型针对两种常见用例进行了优化:(1) 当给定键的条目仅写入一次但读取多次时,如在只会增长的缓存中,或者 (2) 当多个 goroutine 读取、写入和读取时覆盖不相交的键集的条目。在这两种情况下,与与单独的 Mutex 或 RWMutex 配对的 Go Map 相比,使用 Map 可以显着减少锁争用。
解决这些问题的通常方法是构建使用模型,然后对其进行基准测试。
例如,由于“缓存未命中很少见”,因此假设它将Load在大部分时间工作并且仅LoadOrStore在必要时(通过值分配和初始化)工作。
$ go test map_test.go -bench=. -benchmem
BenchmarkHit-4 2 898810447 ns/op 44536 B/op 1198 allocs/op
BenchmarkMiss-4 1 2958103053 ns/op 483957168 B/op 43713042 allocs/op
$
Run Code Online (Sandbox Code Playgroud)
map_test.go:
package main
import (
"strconv"
"sync"
"testing"
)
func BenchmarkHit(b *testing.B) {
for N := 0; N < b.N; N++ {
var m sync.Map
for i := 0; i < 64*1024; i++ {
for k := 0; k < 256; k++ {
// Assume cache hit
v, ok := m.Load(k)
if !ok {
// allocate and initialize value
v = strconv.Itoa(k)
a, loaded := m.LoadOrStore(k, v)
if loaded {
v = a
}
}
_ = v
}
}
}
}
func BenchmarkMiss(b *testing.B) {
for N := 0; N < b.N; N++ {
var m sync.Map
for i := 0; i < 64*1024; i++ {
for k := 0; k < 256; k++ {
// Assume cache miss
// allocate and initialize value
var v interface{} = strconv.Itoa(k)
a, loaded := m.LoadOrStore(k, v)
if loaded {
v = a
}
_ = v
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
4645 次 |
| 最近记录: |