需要一种策略来缓存有关对象的信息而不使其保持活动状态

时间:2018-06-12 22:04:57

标签: c# performance caching

我有一个处理对象的库(碰巧是不可变的)。很多时候,它一遍又一遍地处理相同的对象,但它无法知道,所以它会做一堆重复的工作。

一个很好的类比可能是我正在渲染一个界面。我的渲染函数返回一个帧,但是渲染该帧的99%的工作被使用和丢弃。如果下次我被要求渲染包含该控件的帧时,大部分工作都可以重复使用,我认识到它是相同的控件(可能只是略有不同的状态)。

如果这是现实生活,我和人打交道,我会给每个人一张“身份证”,以便他们下次访问时给我回复,这样如果我看到的话,我可以在我的数据库中查找他们又来了。不幸的是,在大多数编程语言(包括C#)中,您不能仅使用自定义数据“增强”任意对象,以便以后更容易识别它们。

我考虑过保留以前见过的对象缓存:

Dictionary<Object, ReallyExpensiveInformation> cache = new ...;

或者,如果我们担心错误地覆盖“等于”的对象:

Dictionary<CachedObject, ReallyExpensiveInformation> cache = new ...;
// Where CachedObject is defined as:
private class CachedObject : IEquatable<CachedObject> {
    Object Value { get; set; }
    // Get the native hash code that is based on the object reference
    override int GetHashCode() => RuntimeHelpers.GetHashCode(Value);
    // Ensure each new reference is processed, even if they would claim to be equivalent
    override bool Equals(CachedObject other) => ReferenceEquals(Value, other.Value);
}

这些方法在我的脑海中都遇到了同样的核心问题,即他们通过坚持可能已经最终确定的对象来创建内存泄漏

我说我一遍又一遍地遇到相同的物体,但我遇到了许多我再也见不到的物体。我有足够的空间来存储ReallyExpensiveInformation,因为最终结果很小,但Objects可能很大,我不知道调用者持有什么,以及他们是什么正在放弃收集垃圾。调用者可能会或可能不会保留对这些对象的大量引用,但如果他们决定使用它们,我不希望我的缓存成为阻止它们被垃圾收集的东西。

我几乎希望有一些方法可以挂钩一个对象的“引用计数”,并在我最后一次保持时转储它。

无论如何,我的下一个想法是拥有自己的“GarbageCollection”阶段。使用上次访问时间增强我的字典,并清除一段时间内未重复使用的所有条目。这是一个需要解决的复杂问题。我想知道我是否完全忽略了一些简单的解决方案。

希望很明显我无法控制我正在处理的对象,否则我会增强它们的对象模型。

1 个答案:

答案 0 :(得分:0)

所以,@ Eser刚刚转向System.WeakReference。让我们看看它会是什么样子:

private class CachedReference : IEquatable<CachedObject> {
    private readonly int _hash;
    public WeakReference Value { get; }
    public CachedReference (Object obj) {
        Value = new WeakReference(obj);
        _hash = RuntimeHelpers.GetHashCode(Value);
    }
    override int GetHashCode() => _hash;
    // If our value is garbage collected, we'll stop matching anything
    override bool Equals(CachedObject other) =>
        Value != null && ReferenceEquals(Value, other.Value);
}

ConcurrentDictionary<CachedReference, ReallyExpensiveInformation> cache = new ...;

// And we use it like this
public ReallyExpensiveInformation GetOrCompute(Object obj) =>
    return cache.GetOrAdd(new CachedReference(obj), key => ComputeExpensiveInfo(key.Value));

看起来我们仍然需要一些流程来收集&#34; Garbage Collect&#34;已悬空的字典条目:

  • 节省字典条目,密钥和缓存值消耗的内存
  • 使字典的大小保持较小/因此可以快速查找。

或许这样的事情?

Timer garbageCollection = new Timer { AutoReset = true; Interval = 1000 };
Timer.Elapsed += () => cache.Keys.Where(k => k.Value == null)
                                 .ToList().ForAll(k => cache.TryRemove(k, out _));

这有点严重,我不喜欢周期性 O(N) 删除干扰缓存的想法。 (即&#34;冻结&#34;或在清理或更换时减慢访问速度)。

绝对比我们好,但我想知道是否有更优雅的事情可以做清理。