我经常读到异常有点慢,如果性能是一个问题(例如,在 Java、F# 等中),应该避免。这是否适用于常见的 OCaml 函数,例如Hashtbl.find
返回未找到元素的异常?
特别是,如果我希望我的应用程序高效,我是否应该始终使用例如Hashtable.mem
在调用之前测试元素成员资格Hashtbl.find
?或者函数的额外比较mem
会对性能产生负面影响?
我经常读到异常有点慢,如果性能是一个问题(例如,在 Java、F# 等中),应该避免。这是否适用于常见的 OCaml 函数,例如Hashtbl.find
返回未找到元素的异常?
特别是,如果我希望我的应用程序高效,我是否应该始终使用例如Hashtable.mem
在调用之前测试元素成员资格Hashtbl.find
?或者函数的额外比较mem
会对性能产生负面影响?
OCaml 异常处理使引发和捕获异常的速度非常快——请参阅此 SO 线程以了解有关其实现方式的内部详细信息。我没有注意精确地对其进行基准测试,但我的随机猜测是它在间接函数调用的范围内。
众所周知,与语言的其余部分相比,OCaml 异常比 F# 的异常要快得多——这给将代码从 OCaml 移植到 F# 的人们带来了性能问题。在 OCaml 中,异常不会导致性能问题。
Hashtbl.mem
之前调用Hashtbl.find
可能比捕获异常慢。惯用的风格往往是try Hashtbl.find .. with Not_found -> ...
。
也就是说,OCaml 社区有一个明智的运动,即使用更明确的错误处理风格,使用option
类型而不是异常。基本原理不是基于性能,而是基于类型检查器阻止您忘记处理错误情况的事实。在设计全新的 API 时,我更愿意这样做。使用引发异常的第三方函数时,请确保立即捕获所有可能的异常;否则通常是一种设计气味,应该非常合理。
由于方便,OCaml 异常也经常被用作纯控制流机制(而不是表示罕见的故障情况)。你会遇到如下代码:
try
for i = 0 to .... do
if .. then raise Exit
done; false
with Exit -> true
Finally, I feel that you might be taking a bad approach to implementation choices. Asking general micro-questions about performances is generally not the way to go. Think of correctness and readability first. Performance questions should generally come later, and only in situation where it is measurable/profilable.
首先回答Hashtbl.mem
之前的具体问题Hashtbl.find
:不要这样做,因为检查表中元素是否存在必须不要两次;虽然两种策略的成本都是O(1),但首先调用Hashtbl.mem
将计算哈希值和查找两次——这可能比获取异常花费更长的时间。
作为一般建议,仅在异常概率较低时创建引发异常的函数。类型系统不考虑异常,因此更健壮的实现将具有'a option
返回值而不是'a
加上可能的异常。ocaml 核心库使用后缀 of_exn
来明确一个函数可能会抛出异常,但通常更喜欢非异常抛出的函数。
因此,对于哈希表,您应该(在完美的世界中)具有两个功能:
Hashtbl.find_exn : 'a t -> key -> 'a (* throws Not_found *)
Hashtbl.find : 'a t -> key -> 'a option
如果有疑问,请使用第二个。如果不是为了纯粹的速度,您可以在原始哈希表周围编写一个简单的包装器:
find_safe h k = try Some (Hashtbl.find h k) with Not_found -> None
I've often read that exceptions are somewhat slow and should be avoided if performance is an issue (for instance, in Java, F#, etc). Does that apply to common OCaml functions such as Hashtbl.find, which return exceptions for elements not found?
No. Folklore around exceptions being slow and for exceptional circumstances in mainstream languages is nonsense. Exceptions are just another control flow construct. OCaml's exceptions are fast and commonly used for non-exceptional circumstances. Last I looked (a few years ago) exceptions were around 6x faster in OCaml than C++, around 100x faster than Java and around 600x faster than .NET.
People sometimes avoid exceptions in OCaml not for performance-related reasons but because they want explicit local control flow, typically forcing the caller to match
over a success/failure union type instead of potentially non-local exception propagation. They do this to improve correctness, which is more important than performance.