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

基于物理身份的Hashtbl.hash替代方案,hash,ocaml,referenceequals,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

我试图导出一个描述结构化值的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)代理用于引用标识

答案是否定的

我讨厌在状态上附加序列号,因为这是诊断代码,所以我所做的任何错误都有可能掩盖其他错误。

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


您还可以使用
Hashtbl.hash_param
(请参阅)在散列过程中打开值遍历旋钮。请注意,Hashtbl代码对具有相同散列值的存储桶使用链接列表,因此有大量散列冲突将触发线性搜索行为。最好使用二进制搜索树对冲突存储桶进行其他实现。不过,您应该在或者转向更复杂的(在“好的情况下”性能更差)解决方案。

我发现使用物理等式进行散列非常棘手。您当然不能使用值的地址之类的内容作为散列键,因为(如您所说)GC会改变一切。一旦有了散列键,似乎只要值是可变的,就可以使用物理相等来进行比较。如果值是不可变的,OCaml不能保证(=)的含义。实际上,相等的不可变对象(=)如果OCaml编译器或运行时愿意,理论上可以合并到单个物理对象中(反之亦然)


当我研究各种可能性时,当我需要一个唯一id时,我通常会在我的值中加入一个序列号。正如gasche所说,如果你的值是实际的Oo样式对象,你可以使用
Oo.id

像其他人一样,我认为唯一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 模块类型Intf= 信号 类型t=专用{ id:int; foo:string; } val创建\u t:foo:string->t 结束 模块Impl:Intf= 结构 类型t={ id:int; foo:string; } 让我们创建您的id= 设n=ref 0 in 乐趣()-> 如果!n=-1,那么 带有“超出唯一ID”的故障 否则( 增量n; N ) 让我们创建\u t~foo={ id=create_id(); 福 } 结束
很抱歉我做了这么难看的黑客,但我不久前做了这样的东西

这方面的诀窍是确保在表中插入值后不会在内存中移动值。有两种情况可以移动内存中的值:从次堆复制到主堆和主堆压缩。这意味着在表中插入值时,它必须在主堆中,并且在表上的两个操作之间您必须确保没有发生压缩

检查值是否在次要堆中可以使用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之前)中看到一些非常丑陋的东西压缩使值在内存中保持相同的顺序,因此您可以实现基于物理地址的集合或映射,而无需担心。

感谢指针。所谓对象,我指的是结构化值,而不是
类的实例。
。我想您的
sig
丢失了
val create\u t:~foo:string->t
我想我会用光的在尝试依赖于如此多的实现细节的东西之前,请使用所有其他方法,但感谢您解释股票GC的相关细节。“Hashtbl.hash的文档表明,它适合在StateIdentity.equal=(=)和StateIdentity.equal=(=)时使用。”但事实并非如此。
Hashtbl.hash
与物理相等关联时会发生许多冲突,这意味着如果您使用它,您的哈希表可能会退化为一个由结构相等、物理上不同的键组成的长列表组成的短数组。@PascalCuoq,非常正确。我所说的“合适”是指“保持替换和查找不变”,并不是指保持查找上的键比较数不变。