Clojure 多方法本质上很慢吗
我正在查看 clojure.core 函数重新分组:
(defn re-groups [^java.util.regex.Matcher m]
(let [gc (. m (groupCount))]
(if (zero? gc)
(. m (group))
(loop [ret [] c 0]
(if (<= c gc)
(recur (conj ret (. m (group c))) (inc c))
ret)))))
并认为将其重写为“更好”多方法:
(defmulti re-groups (fn [^java.util.regex.Matcher m] (.groupCount m)))
(defmethod re-groups 0 [m] (.group m))
(defmethod re-groups :default [m]
(let [idxs (range (inc (.groupCount m)))]
(reduce #(conj %1 (.group m %2)) [] idxs)))
然而,当比较时间时,我惊讶地发现重写速度慢了 4 倍:
clojure.core: "Elapsed time: 668.029589 msecs"
multi-method: "Elapsed time: 2632.672379 msecs"
这是多方法的自然结果还是这里还有其他问题?
I was looking at the clojure.core function re-groups:
(defn re-groups [^java.util.regex.Matcher m]
(let [gc (. m (groupCount))]
(if (zero? gc)
(. m (group))
(loop [ret [] c 0]
(if (<= c gc)
(recur (conj ret (. m (group c))) (inc c))
ret)))))
And thought that it would be "better" to rewrite it as a multimethod:
(defmulti re-groups (fn [^java.util.regex.Matcher m] (.groupCount m)))
(defmethod re-groups 0 [m] (.group m))
(defmethod re-groups :default [m]
(let [idxs (range (inc (.groupCount m)))]
(reduce #(conj %1 (.group m %2)) [] idxs)))
Yet when comparing the times I was surpised to see the rewrite is 4 times slower:
clojure.core: "Elapsed time: 668.029589 msecs"
multi-method: "Elapsed time: 2632.672379 msecs"
Is this a natural result of multimethods or is there something else wrong here?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
Clojure 多方法允许基于任意调度函数的运行时多态行为。这对于构建临时层次结构和抽象非常强大,但您会因为这种灵活性而付出性能损失。您可能希望使用协议重新实现您的解决方案。仅当您需要完整的运行时类型灵活性时才使用多方法。
Clojure multimethods allow runtime polymorphic behavior based on arbitrary dispatch functions. This is very powerful for building ad-hoc hierarchies and abstractions, but you pay a performance hit for such flexibility. You may wish to re-implement your solution with a protocol. Only use multimethods when you need complete runtime type flexibility.
一般来说,任何做得更多的事情都会花费更多的时间。因为多方法提供了多种调度方式,所以它们比协议花费的时间更长,我必须回答你的问题“是的,与协议相比”。
在实践中,从协议开始,并在必要时使用多种方法(看起来您需要它们)。 你不能用代码让计算机变得更快,但你可以让它做得更少
in general anything that does more will take more time. because multimethods offer a variety of ways to dispatch they will take longer than protocols you I have to answer your question "yes, when compared to protocols".
In practice start with protocols and go to multimethods when you have to (and it looks like you need them). you can't make the computer faster with code, but you can make it do less
我认为你的多方法实现速度较慢的原因也可能是因为你在惰性序列(由范围提供)上使用reduce,而不是在clojure.core中使用的递增索引上使用循环/递归。尝试将 clojure.core/re-groups 实现的循环部分复制到第二个 defmethod 中,看看这是否不会提高性能。
如果没有看到您的测试用例,很难说,但是如果匹配器中有很多组,那么您可能会花费比多方法调度更多的时间将组减少为向量。如果组很少,那么多方法调度将占据所花费时间的更重要部分。无论哪种情况,采用相同的实现都将消除造成时间差异的潜在影响因素,并帮助您更好地了解多方法调度中的实际开销。
您可能要考虑的另一件事是,速度放缓可能是由于反射造成的。尝试将 *warn-on-reflection* 设置为 true 并看看它是否抱怨。也许另一种策略类型的提示可能会有所帮助。
I think the reason your multi-method implementation is slower may also be because you are using reduce on a lazy sequence (provided by range) instead of the loop/recur on an incrementing index that is used in clojure.core. Try copying the loop part of the implementation of clojure.core/re-groups into your second defmethod and see if that doesn't increase the performance.
It is hard to say without seeing your test case, but if there are a lot of groups in the matcher, you may be spending much more time in reducing the groups into a vector than in the multi-method dispatch. If there are few groups though, then the multi-method dispatch will be a more significant portion of the time spent. In either case, having the same implementation will eliminate a potential contributing factor to the timing difference and help you to get a better feel for the actual overhead in multi-method dispatch.
Another thing you might consider is the possibility that the slowdown is due to reflection. Try setting *warn-on-reflection* to true and see if it complains. Maybe another strategic type hint may help.