问题描述
我正在 Golang 中实现缓存。假设缓存可以实现为 sync.Map,整数键和值作为结构:
type value struct {
fileName string
functionName string
}
大量记录具有相同的 fileName
和 functionName
。为了节省内存,我想使用字符串池。 Go 有不可变的字符串,我的想法是这样的:
var (
cache sync.Map
stringPool sync.Map
)
type value struct {
fileName string
functionName string
}
func addRecord(key int64,val value) {
fileName,_ := stringPool.LoadOrStore(val.fileName,val.fileName)
val.fileName = fileName.(string)
functionName,_ := stringPool.LoadOrStore(val.functionName,val.functionName)
val.functionName = functionName.(string)
cache.Store(key,val)
}
我的想法是将每个唯一的字符串(fileName
和 functionName
)保存在内存中一次。它会起作用吗?
缓存实现必须是并发安全的。缓存中的记录数约为 10^8。字符串池中的记录数约为10^6。
您能否建议如何管理字符串池大小?
我正在考虑为字符串池中的每条记录存储引用计数。它将需要额外的同步或可能需要全局锁来维护它。我想实现尽可能简单。您可以在我的代码片段中看到我没有使用额外的互斥锁。
或者我可能需要遵循完全不同的方法来最小化缓存的内存使用量?
解决方法
您尝试使用 stringPool
执行的操作通常称为 string interning。有像 github.com/josharian/intern 这样的库可以为这类问题提供“足够好”的解决方案,并且不需要您手动维护 stringPool
映射。请注意,没有任何解决方案(包括您的解决方案,假设您最终从 stringPool
中删除了一些元素)可以可靠地对 100% 的字符串进行重复数据删除,而不会产生不切实际的 CPU 开销水平。
作为旁注,值得指出的是 sync.Map
是 not really designed for update-heavy workloads。根据所使用的 key
,您在调用 cache.Store
时实际上可能会遇到严重的争用。此外,由于 sync.Map
依赖于 interface{}
的键和值,因此它通常会比普通的 map
产生更多的分配。请务必使用实际工作负载进行基准测试,以确保您选择了正确的方法。