从字符串池中驱逐未使用的记录的最佳方法是什么?

问题描述

我正在 Golang 中实现缓存。假设缓存可以实现为 sync.Map,整数键和值作为结构:

type value struct {
    fileName     string
    functionName string
}

大量记录具有相同的 fileNamefunctionName。为了节省内存,我想使用字符串池。 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)
}

我的想法是将每个唯一的字符串(fileNamefunctionName)保存在内存中一次。它会起作用吗?

缓存实现必须是并发安全的。缓存中的记录数约为 10^8。字符串池中的记录数约为10^6。

我有一些从缓存中删除记录的逻辑。主缓存大小没有问题。

您能否建议如何管理字符串池大小?

我正在考虑为字符串池中的每条记录存储引用计数。它将需要额外的同步或可能需要全局锁来维护它。我想实现尽可能简单。您可以在我的代码片段中看到我没有使用额外的互斥锁。

或者我可能需要遵循完全不同的方法来最小化缓存的内存使用量?

解决方法

您尝试使用 stringPool 执行的操作通常称为 string interning。有像 github.com/josharian/intern 这样的库可以为这类问题提供“足够好”的解决方案,并且不需要您手动维护 stringPool 映射。请注意,没有任何解决方案(包括您的解决方案,假设您最终从 stringPool 中删除了一些元素)可以可靠地对 100% 的字符串进行重复数据删除,而不会产生不切实际的 CPU 开销水平。

作为旁注,值得指出的是 sync.Mapnot really designed for update-heavy workloads。根据所使用的 key,您在调用 cache.Store 时实际上可能会遇到严重的争用。此外,由于 sync.Map 依赖于 interface{} 的键和值,因此它通常会比普通的 map 产生更多的分配。请务必使用实际工作负载进行基准测试,以确保您选择了正确的方法。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...