我注意到queue.ml由于基于链表(带有可变字段)的实现而趋于相当慢
是否可以构建具有更快结构的队列,例如数组?
或者有没有办法实现更好的表现,但仍然是功能性的?
编辑:我想实现一个发布 - 订阅解决方案,成千上万的人在不到一毫秒的时间内排队并提供操作(对于图形交互式应用程序),所以我需要非常快速的东西和ocaml队列实现没有满足我的性能需求
答案 0 :(得分:5)
知道为什么你认为基于链表的实现很慢会很有趣。这是队列的经典实现。插入和删除速度与我想象的一样快(更新一个或两个参考字段)。请注意,我没有查看您正在谈论的代码。
您可以使用数组实现环形缓冲区。某些事情可能会更快(比如遍历所有元素),但会有最大的大小(对于最简单的实现)。初始化阵列也有复杂性。
一般来说,数据结构的功能(不可变)实现比相应的命令式(可变)版本慢一点。有一个非常好的功能队列实现,具有很好的性能。基本技巧是将队列的一半保持在正向顺序(快速删除),将一半保持在相反的顺序(快速插入)。逆转的额外传球引入了一个小的惩罚,就像我说的那样。
如果您正在查看内存(分配和GC),则不可变方法将分配最多,经典方法为中等量,而环形缓冲区将分配很少。但是一个好的FP实现(比如OCaml)可以非常快速地分配内存,如果对象是短暂的,那么回收它也不会太慢。
编辑:为了它的价值,我只是在我的笔记本电脑(3.06 GHz Intel Core 2 Duo)上进行了非常粗略的时序测试,我可以使用标准OCaml将一百万个值排队并出列队列模块在70毫秒。那么大约需要.7毫秒来做10,000(如果我做对了)。如果你的CPU速度不快于我的速度,那么做你想做的事情似乎不够快。
编辑2:我只是手写了一些代码,假设您的值中有一个预先存在的引用字段可用于排队。这避免了在排队代码中进行任何分配,但在其他方面类似于标准的Queue模块(尽管不那么优雅)。在与上面相同的笔记本电脑上,一百万个队列和出列需要大约48毫秒。所以它的速度要快一点。
编辑3:您很可能现在已经得到了答案,但我刚刚使用上面列出的纯队列实施进行了粗略的计时测试,我看到大约18毫秒来做一个百万队列和队列。所以它的速度要快得多。这是合理的,因为OCaml针对纯计算进行了调整。这是我的代码;你可以检查是否有任何错误。
type q = {
head: int list;
tail: int list;
}
let q_add q i =
{ q with tail = i :: q.tail }
let q_take q =
match q.head with
| [] ->
begin
match List.rev q.tail with
| [] -> raise Not_found
| h :: t -> (h, { head = t; tail = [] })
end
| h :: t -> (h, { head = t; tail = q.tail })
let main () =
let q = q_add { head = []; tail = [] } 0
in let now = Unix.gettimeofday ()
in let rec go q i =
if i > 1_000_000 then
q
else
let (_, q') = q_take (q_add q i)
in
go q' (i + 1)
in let _ = go q 1
in let duration = Unix.gettimeofday () -. now
in
Printf.printf "%f\n" duration
let () = main ()
像我说的那样,这是一个粗糙的考验。队列的长度仅为1到2个元素。我不确定结果会在您的环境中保持不变。但这很有趣。
答案 1 :(得分:4)
您需要测量操作以真正量化任何给定的队列操作实现效率低下。
也就是说,针对不同的队列场景存在替代数据结构。特别是如果您对持久队列或纯队列或并发队列感兴趣,那么您有几个选择。
纯数据结构
并发队列
发布订阅
如果您正在查看发布/订阅机制,可以考虑绑定到zeromq:
它肯定是针对高频操作,甚至支持FP社区,http://www.zeromq.org/bindings:haskell
ZeroMQ有两种不同的OCaml绑定:http://www.zeromq.org/bindings:ocaml