基于物理身份的替代Hashtbl.hash

时间:2012-10-24 15:40:28

标签: hash ocaml referenceequals

我正在尝试派生一个描述结构化值的Graphviz文件。这是出于诊断目的,所以我希望我的图形尽可能地镜像内存中的实际结构。我正在使用下面的值将值映射到Graphviz顶点,这样当值有两个或多个入站引用时我可以重用一个顶点:

let same = (==)

module StateIdentity : Hashtbl.HashedType = struct
  type t = R.meta_t state
  let hash = Hashtbl.hash
  let equal = same
end

module StateHashtbl = Hashtbl.Make (StateIdentity)

Hashtbl.hash的文档表明它适合在StateIdentity.equal = (=)StateIdentity.equal = (==)时使用,但我想确保哈希表访问与O(1)接近)尽可能不要在每次查找时Hashtbl.hash走一条(在这种情况下可能很大)对象图。

我知道Ocaml会移动引用,但Ocaml中是否有可用于引用标识的O(1)代理?

Hashtable of mutable variable in Ocaml的答案表明没有。

我不愿意将序列号附加到状态,因为这是诊断代码,因此我所做的任何错误都有可能掩盖其他错误。

4 个答案:

答案 0 :(得分:6)

如果您在OCaml的< ... >对象类型的意义上使用“对象”一词,那么您可以使用Oo.id为每个实例获取唯一的整数标识。否则,“是否存在价值认同的一般代理”的答案是“否”。在这种情况下,我的建议是从Hashtbl.hash开始,评估它是否符合您的需要,并设计自己的散列函数。

您还可以使用Hashtbl.hash_param(请参阅documentation)在散列过程中打开值遍历旋钮。请注意,Hashtbl代码使用链接列表来存储相同哈希值的存储区,因此存在大量哈希冲突将触发线性搜索行为。使用二进制搜索树转移到冲突桶的其他实现可能更好。但话又说回来,你应该先评估你的情况,然后再转向更复杂的(并且在“好的情况下”)解决方案中表现更差。

答案 1 :(得分:5)

我发现使用物理相等来进行散列非常棘手。你当然不能使用类似地址的东西作为你的哈希键,因为(如你所说)事情会被GC移动。一旦你有了一个哈希键,只要你的值是可变的,你似乎可以使用物理相等来进行比较。如果您的值不可变,则OCaml不能保证(==)的含义。实际上,如果OCaml编译器或运行时希望(或反之亦然),理论上可以将等于(=)的不可变对象合并为单个物理对象。

当我处理各种可能性时,我通常最终会在需要唯一ID时将序列号放入我的值中。如gasche所说,如果您的值是实际的OO风格对象,则可以使用Oo.id

答案 2 :(得分:4)

与其他人一样,我认为唯一的ID是可行的方式。

唯一ID不难安全生成。一种解决方案是使用如下所谓的私人记录。它会阻止模块的用户复制id字段:

module type Intf =
sig
  type t = private {
    id : int;
    foo : string;
  }

  val create_t : foo: string -> t
end

module Impl : Intf =
struct
  type t = {
    id : int;
    foo : string;
  }

  let create_id =
    let n = ref 0 in
    fun () ->
      if !n = -1 then
        failwith "Out of unique IDs"
      else (
        incr n;
        !n
      )

  let create_t ~foo = {
    id = create_id ();
    foo
  }
end

答案 3 :(得分:2)

很抱歉这个丑陋的黑客,但我不久前做了类似的事情。

关于这一点的诀窍是确保在插入表格后不会在内存中移动值。有两种情况可以在内存中移动值:从次要复制到主堆和主堆压缩。这意味着当您在表中插入值时,它必须位于主堆中,并且必须确保不会发生压缩。

可以使用C函数is_young检查值是否在次要堆中,如果是这种情况,可以使用Gc.minor()强制该值迁移到主堆。

对于第二个问题,您可以完全停用压缩或在压缩上重建表。可以使用

禁用它
Gc.set { Gc.get () with Gc.max_overhead = max_int }

通过在每次访问表中比较

返回的数字,可以检测到发生了压缩
( Gc.quick_stat () ).Gc.compactions

请注意,在访问表之前必须禁用压缩。 如果禁用压缩,则还应考虑更改分配策略以避免堆的无限制碎片。

Gc.set {(Gc.get ()) with Gc.allocation_policy = 1}

如果你想在旧版本的OCaml(4.00之前)中看到一些非常难看的东西,那么压缩会使内存中的值保持相同的顺序,因此你可以根据物理地址实现一个集合或映射而不用担心。

相关问题