【问题标题】:Hash-consing in F# and weak hash tables in .netF# 中的哈希计算和 .net 中的弱哈希表
【发布时间】:2013-03-15 01:38:00
【问题描述】:

Hash-consing 包括在内存中只保留给定对象的一个​​副本;也就是说,如果两个对象在语义上是相等的(相同的内容),那么它们应该在物理上是相等的(在内存中的相同位置)。该技术通常通过保持一个全局哈希集并仅当它们不等于哈希集中的对象时才创建新对象来实现。

另外一个要求是,如果哈希表中的对象没有被哈希表以外的任何东西引用,则它们应该是可收集的;否则,哈希表应该包含弱引用。

由于需要有恒定的时间,因此问题更加复杂,因此需要进行浅层、散列和相等测试;因此,对象具有唯一标识符,当新对象添加到表中时,该标识符会递增。

我有一个使用System.Collections.Generic.Dictionary<key, node> 的工作实现,其中key 是一个元组,给出了节点的浅层摘要(适用于默认散列和相等测试),node 是对象。唯一的问题是 Dictionary 保持对节点的强引用!

我可以使用DictionaryWeakReference,但这不会释放指向悬空引用的键。

有些人提倡使用System.Runtime.CompilerServices.ConditionalWeakTable,但这个类似乎做了相反的事情:它在收集键时释放值,而我需要在收集值时释放键。

可以尝试使用System.Runtime.CompilerServices.ConditionalWeakTable<node, node>,但我需要自定义散列和相等性测试...并且ConditionalWeakTable 被记录使用GetHashCode() 虚拟方法,而不是使用默认散列功能。

因此我的问题是:是否有一些等价的 Dictionary 可以保留对值的弱引用并在引用悬空时释放键?

【问题讨论】:

  • 取值后是否需要立即释放key?或者您可以放宽要求,并在稍后的某个时间点释放密钥?
  • 我不需要立即释放它们——只是我不希望它们堆积并无用地消耗大量内存。我考虑过运行另一个线程来定期杀死带有悬空引用的键,但这似乎很复杂并且容易出现并发错误。
  • 您能否在 F# 中使用 OCaml 代码作为参考实现来实现弱哈希表? IIRC 弱哈希集使用弱数组,可以使用 Array 来实现。
  • 另外,DependentHandle 可能会有所帮助:Ephemerons in .NET and C#

标签: .net f# hashmap hashset


【解决方案1】:

你说得对,CWT 没有解决散列问题,因为它引出了问题——它的键假设引用相等。但是,可能值得指出的是,CWT 不保留键或值。这是一个小测试:

open System.Collections.Generic
open System.Runtime.CompilerServices

let big () =
    ref (Array.zeroCreate (1024 * 1024) : byte [])

let test1 () =
    let d = Dictionary(HashIdentity.Reference)
    for i in 1 .. 10000 do
        stdout.WriteLine(i)
        let big = big ()
        d.Add(big, big)
    d

let test2 () =
    let d = ConditionalWeakTable()
    for i in 1 .. 10000 do
        stdout.WriteLine(i)
        let big = big ()
        d.Add(big, big)
    d

在我的机器上,test1 内存不足,test2 成功。似乎只有在 CWT 不保留键和值时才会发生这种情况。

对于哈希计算,您最好的选择可能是 Artem 在 cmets 中建议的内容。如果这听起来太复杂,那么只给用户控制权也很有意义,比如:

let f = MyFactory() // a dictionary with weak reference values hidden inside
f.Create(..) : MyObject // MyObject has no constructors of its own
f.Cleanup() // explicitly cleans up entries for collected keys 

那么你就不需要引入线程、研究 GC 内部是如何工作的,或者做任何魔法。库的用户可以决定在哪里清理或简单地“忘记”工厂对象 - 这将收集整个表。

【讨论】:

  • 我尝试使用 CWT,但似乎立即收集了放入表中的数据(因为一旦密钥变得无法访问,就会收集值)。您是否尝试过从 CWT 恢复数据?从 A 到 A 使用 CWT 是不可能的,因为 CWT 使用数据类型中的 hashcode 函数,而是调用默认的 hash 函数,这不适合 hash-consing(需要浅散列具有唯一标识符)。一种解决方案是复制 CWT 源代码并对其进行调整。
  • @monniaux:是的,我同意 CWT 不适合哈希计算。 OCaml 弱表显然在这里获胜。从 CWT 中恢复数据很好,但如果您持有密钥 - 这就是它的设计目的。是的,如果您找到一个好的解决方案或编写自己的解决方案,请在此处发布 - 用于哈希计算。
猜你喜欢
  • 2014-05-20
  • 2016-06-23
  • 1970-01-01
  • 1970-01-01
  • 2012-06-03
  • 1970-01-01
  • 2022-07-06
  • 1970-01-01
  • 2012-07-12
相关资源
最近更新 更多