2024 Clojure 状态调查! 分享您的想法。

欢迎!请参阅关于页面了解此工作原理的更多信息。

+13
协议
目前 `satisfies?` 并未使用与协议方法相同的实现缓存,这使得它在实际应用中运行速度过慢。

使用

(defprotocol p (f [_]))
(deftype x [])
(deftype y [])
(extend-type x p (f [_]))


修改前

(let [s "abc"] (bench (instance? CharSequence s))) ;; 平均执行时间:1.358360 纳秒
(let [x (x.)] (bench (satisfies? p x))) ;; 平均执行时间:112.649568 纳秒
(let [y (y.)] (bench (satisfies? p y))) ;; 平均执行时间:2.605426 微秒


*原因:* `satisfies?` 会调用 `find-protocol-impl` 来检查一个对象是否实现了协议,这会检查 x 是否是协议接口的实例或者 x 的类是否是协议实现之一(或者是否在继承链中可以这样认定)。这个检查相对昂贵且未进行缓存。

*建议:* 将协议的方法实现缓存扩展到处理并缓存实例检查(包括负结果)。

修改后

(let [x (x.)] (bench (satisfies? p x))) ;; 平均执行时间:79.321426 纳秒
(let [y (y.)] (bench (satisfies? p y))) ;; 平均执行时间:77.410858 纳秒


*补丁*: CLJ-1814-v7.patch(取决于 CLJ-2426)

共有25个答案

0

评论由:michaelblume

非常好。在重构之前,Honeysql 的大部分时间都花在了 `satisfies?` 调用上。

0

评论由:michaelblume

我认识到这是一个很难复制的严重错误,但是如果您克隆核心.match,将其Clojure依赖项指向1.8.0-master-SNAPSHOT,启动REPL,并通过vim连接到REPL,然后重新加载clojure.core.match,我会得到以下内容:

`
|| java.lang.Exception: 命名空间 'clojure.tools.analyzer.jvm.utils' 未找到,编译:(clojure/tools/analyzer/jvm.clj:9:1)
zipfile:/Users/michael.blume/.m2/repository/org/clojure/clojure/1.8.0-master-SNAPSHOT/clojure-1.8.0-master-SNAPSHOT.jar::clojure/core.clj|5647| clojure.core$throw_if.invokeStatic
zipfile:/Users/michael.blume/.m2/repository/org/clojure/clojure/1.8.0-master-SNAPSHOT/clojure-1.8.0-master-SNAPSHOT.jar::clojure/core.clj|5733| clojure.core$load_lib.invokeStatic
|| clojure.core$load_lib.doInvoke(core.clj)
|| clojure.lang.RestFn.applyTo(RestFn.java:142)
zipfile:/Users/michael.blume/.m2/repository/org/clojure/clojure/1.8.0-master-SNAPSHOT/clojure-1.8.0-master-SNAPSHOT.jar::clojure/core.clj|647| clojure.core$apply.invokeStatic
zipfile:/Users/michael.blume/.m2/repository/org/clojure/clojure/1.8.0-master-SNAPSHOT/clojure-1.8.0-master-SNAPSHOT.jar::clojure/core.clj|5765| clojure.core$load_libs.invokeStatic
|| clojure.core$load_libs.doInvoke(core.clj)
|| clojure.lang.RestFn.applyTo(RestFn.java:137)
zipfile:/Users/michael.blume/.m2/repository/org/clojure/clojure/1.8.0-master-SNAPSHOT/clojure-1.8.0-master-SNAPSHOT.jar::clojure/core.clj|647| clojure.core$apply.invokeStatic
zipfile:/Users/michael.blume/.m2/repository/org/clojure/clojure/1.8.0-master-SNAPSHOT/clojure-1.8.0-master-SNAPSHOT.jar::clojure/core.clj|5787| clojure.core$require.invokeStatic
|| clojure.core$require.doInvoke(core.clj)
|| clojure.lang.RestFn.invoke(RestFn.java:703)
zipfile:/Users/michael.blume/.m2/repository/org/clojure/tools.analyzer.jvm/0.6.5/tools.analyzer.jvm-0.6.5.jar::clojure/tools/analyzer/jvm.clj|9| clojure.tools.analyzer.jvm$eval4968$loading5561auto__4969.invoke
zipfile:/Users/michael.blume/.m2/repository/org/clojure/tools.analyzer.jvm/0.6.5/tools.analyzer.jvm-0.6.5.jar::clojure/tools/analyzer/jvm.clj|9| clojure.tools.analyzer.jvm$eval4968.invokeStatic
|| clojure.tools.analyzer.jvm$eval4968.invoke(jvm.clj)
|| clojure.lang.Compiler.eval(Compiler.java:6934)
|| clojure.lang.Compiler.eval(Compiler.java:6923)
|| clojure.lang.Compiler.load(Compiler.java:7381)
|| clojure.lang.RT.loadResourceScript(RT.java:372)
|| clojure.lang.RT.loadResourceScript(RT.java:363)
|| clojure.lang.RT.load(RT.java:453)
|| clojure.lang.RT.load(RT.java:419)
zipfile:/Users/michael.blume/.m2/repository/org/clojure/clojure/1.8.0-master-SNAPSHOT/clojure-1.8.0-master-SNAPSHOT.jar::clojure/core.clj|5883| clojure.core$load$fn
5669.invoke
zipfile:/Users/michael.blume/.m2/repository/org/clojure/clojure/1.8.0-master-SNAPSHOT/clojure-1.8.0-master-SNAPSHOT.jar::clojure/core.clj|5882| clojure.core$load.invokeStatic
zipfile:/Users/michael.blume/.m2/repository/org/clojure/clojure/1.8.0-master-SNAPSHOT/clojure-1.8.0-master-SNAPSHOT.jar::clojure/core.clj|5683| clojure.core$load_one.invokeStatic
|| clojure.core$load_one.invoke(core.clj)
zipfile:/Users/michael.blume/.m2/repository/org/clojure/clojure/1.8.0-master-SNAPSHOT/clojure-1.8.0-master-SNAPSHOT.jar::clojure/core.clj|5728| clojure.core$load_lib$fn5618.invoke
zipfile:/Users/michael.blume/.m2/repository(org)/clojure/tools.analyzer/jvm/0.6.5/tools.analyzer.jvm-0.6.5.jar::clojure/tools/analyzer/jvm.clj|9| clojure.tools.analyzer.jvm$eval4968$loading|5561auto|__4969.invoke
|| clojure.tools.analyzer.jvm$eval4968$loading|5561auto|__4969.invoke(jvm.clj)
|| clojure.lang.Compiler.eval(Compiler.java:6934)
|| clojure.lang.Compiler.eval(Compiler.java:6923)
|| clojure.lang.Compiler.load(Compiler.java:7381)
|| clojure.lang.RT.loadResourceScript(RT.java:372)
|| clojure.lang.RT.loadResourceScript(RT.java:363)
|| clojure.lang.RT.load(RT.java:453)
|| clojure.lang.RT.load(RT.java:419)
jar文件:/Users/michael.blume/.m2/repository/org/clojure/clojure/1.8.0-master-SNAPSHOT/clojure-1.8.0-master-SNAPSHOT.jar::clojure/core.clj|5883| clojure.core$load$fn5669.invoke
jar文件:/Users/michael.blume/.m2/repository/org/clojure/clojure/1.8.0-master-SNAPSHOT/clojure-1.8.0-master-SNAPSHOT.jar::clojure/core.clj|5882| clojure.core$load.invokeStatic
jar文件:/Users/michael.blume/.m2/repository/org/clojure/clojure/1.8.0-master-SNAPSHOT/clojure-1.8.0-master-SNAPSHOT.jar::clojure/core.clj|5683| clojure.core$load_one.invokeStatic
|| clojure.core$load_one.invoke(core.clj)
jar文件:/Users/michael.blume/.m2/repository/org/clojure/clojure/1.8.0-master-SNAPSHOT/clojure-1.8.0-master-SNAPSHOT.jar::clojure/core.clj|5728| clojure.core$load_lib$fn5618.invoke
jar文件:/Users/michael.blume/.m2/repository/org/clojure/clojure/1.8.0-master-SNAPSHOT/clojure-1.8.0-master-SNAPSHOT.jar::clojure/core.clj|5727| clojure.core$load_lib.invokeStatic
|| clojure.core$load_lib.doInvoke(core.clj)
|| clojure.lang.RestFn.applyTo(RestFn.java:142)
jar文件:/Users/michael.blume/.m2/repository/org/clojure/clojure/1.8.0-master-SNAPSHOT/clojure-1.8.0-master-SNAPSHOT.jar::clojure/core.clj|647| clojure.core$apply.invokeStatic
jar文件:/Users/michael.blume/.m2/repository/org/clojure/clojure/1.8.0-master-SNAPSHOT/clojure-1.8.0-master-SNAPSHOT.jar::clojure/core.clj|5765| clojure.core$load_libs.invokeStatic
|| clojure.core$load_libs.doInvoke(core.clj)
|| clojure.lang.RestFn.applyTo(RestFn.java:137)
jar文件:/Users/michael.blume/.m2/repository/org/clojure/clojure/1.8.0-master-SNAPSHOT/clojure-1.8.0-master-SNAPSHOT.jar::clojure/core.clj|647| clojure.core$apply.invokeStatic
jar文件:/Users/michael.blume/.m2/repository/org/clojure/clojure/1.8.0-master-SNAPSHOT/clojure-1.8.0-master-SNAPSHOT.jar::clojure/core.clj|5787| clojure.core$require.invokeStatic
|| clojure.core$require.doInvoke(core.clj)
|| clojure.lang.RestFn.invoke(RestFn.java:421)
|| clojure.core.match$eval4949.invokeStatic(form-init2494799382238714928.clj:1)
|| clojure.core.match$eval4949.invoke(form-init2494799382238714928.clj)
|| clojure.lang.Compiler.eval(Compiler.java:6934)
|| clojure.lang.Compiler.eval(Compiler.java:6897)
jar文件:/Users/michael.blume/.m2/repository/org/clojure/clojure/1.8.0-master-SNAPSHOT/clojure-1.8.0-master-SNAPSHOT.jar::clojure/core.clj|3096| clojure.core$eval.invokeStatic
|| clojure.core$eval.invoke(core.clj)
jar文件:/Users/michael.blume/.m2/repository/org/clojure/clojure/1.8.0-master-SNAPSHOT/clojure-1.8.0-master-SNAPSHOT.jar::clojure/main.clj|240| clojure.main$repl$read_eval_print
7404$fn7407.invoke
jar文件:/Users/michael.blume/.m2/repository/org/clojure/clojure/1.8.0-master-SNAPSHOT/clojure-1.8.0-master-SNAPSHOT.jar::clojure/main.clj|240| clojure.main$repl$read_eval_print
7404.invoke
jar文件:/Users/michael.blume/.m2/repository/org/clojure/clojure/1.8.0-master-SNAPSHOT/clojure-1.8.0-master-SNAPSHOT.jar::clojure/main.clj|258| clojure.main$repl$fn7413.invoke
jar文件:/Users/michael.blume/.m2/repository/org/clojure/clojure/1.8.0-master-SNAPSHOT/clojure-1.8.0-master-SNAPSHOT.jar::clojure/main.clj|258| clojure.main$repl.invokeStatic
|| clojure.main$repl.doInvoke(main.clj)
|| clojure.lang.RestFn.invoke(RestFn.java:1523)
jar文件:/Users/michael.blume/.m2/repository/org/clojure/tools.nrepl/0.2.10/tools.nrepl-0.2.10.jar::clojure/tools/nrepl/middleware/interruptible_eval.clj|58| clojure.tools.nrepl.middleware.interruptible_eval$evaluate$fn
637.invoke
|| clojure.lang.AFn.applyToHelper(AFn.java:152)
|| clojure.lang.AFn.applyTo(AFn.java:144)
jar文件:/Users/michael.blume/.m2/repository/org/clojure/clojure/1.8.0-master-SNAPSHOT/clojure-1.8.0-master-SNAPSHOT.jar::clojure/core.clj|645| clojure.core$apply.invokeStatic
jar文件:/Users/michael.blume/.m2/repository/org/clojure/clojure/1.8.0-master-SNAPSHOT/clojure-1.8.0-master-SNAPSHOT.jar::clojure/core.clj|1874| clojure.core$with_bindingsSTAR.invokeStatic
|| clojure.core$with_bindingsSTAR.doInvoke(core.clj)
|| clojure.lang.RestFn.invoke(RestFn.java:425)
jar文件:/Users/michael.blume/.m2/repository/org/clojure/tools.nrepl/0.2.10/tools.nrepl-0.2.10.jar::clojure/tools/nrepl/middleware/interruptible_eval.clj|56| clojure.tools.nrepl.middleware.interruptible_eval$evaluate.invokeStatic
|| clojure.tools.nrepl.middleware.interruptible_eval$evaluate.invoke(interruptible_eval.clj)
jar文件:/Users/michael.blume/.m2/repository/org/clojure/tools.nrepl/0.2.10/tools.nrepl-0.2.10.jar::clojure/tools/nrepl/middleware/interruptible_eval.clj|191| clojure.tools.nrepl.middleware.interruptible_eval$interruptible_eval$fn
679$fn__682.invoke
jar文件:/Users/michael.blume/.m2/repository/org/clojure/tools.nrepl/0.2.10/tools.nrepl-0.2.10.jar::clojure/tools/nrepl/middleware/interruptible_eval.clj|159| clojure.tools.nrepl.middleware.interruptible_eval$run_next$fn__674.invoke
|| clojure.lang.AFn.run(AFn.java:22)
|| java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
|| java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
|| java.lang.Thread.run(Thread.java:745)

`

在我自己的项目中,当重新加载一个依赖于clojure.core.match的命名空间时,出现了相同的问题。

0

评论者:bronsa

这有没有可能与AOT(预编译)有关呢?

0

评论由:michaelblume

稍微缩小了范围,如果检查 tools.analyzer.jvm,打开一个交互式解释器REPL,执行 (require 'clojure.tools.analyzer.jvm.utils),我将得到以下内容:

|| 类似于:java.lang.ClassCastException: java.lang.Class cannot be cast to clojure.asm.Type,编译:(utils.clj:260:13) || clojure.lang.Compiler$InvokeExpr.eval(Compiler.java:3642) || clojure.lang.Compiler$InvokeExpr.eval(Compiler.java:3636) || clojure.lang.Compiler$DefExpr.eval(Compiler.java:450) || clojure.lang.Compiler.eval(Compiler.java:6939) || clojure.lang.Compiler.load(Compiler.java:7381) || clojure.lang.RT.loadResourceScript(RT.java:372) || clojure.lang.RT.loadResourceScript(RT.java:363) || clojure.lang.RT.load(RT.java:453) || clojure.lang.RT.load(RT.java:419) zipfile:/Users/michael.blume/.m2/repository/org/clojure/clojure/1.8.0-master-SNAPSHOT/clojure-1.8.0-master-SNAPSHOT.jar::clojure/core.clj|5883| clojure.core$load$fn__5669.invoke zipfile:/Users/michael.blume/.m2/repository/org/clojure/clojure/1.8.0-master-SNAPSHOT/clojure-1.8.0-master-SNAPSHOT.jar::clojure/core.clj|5882| clojure.core$load.invokeStatic zipfile:/Users/michael.blume/.m2/repository/org/clojure/clojure/1.8.0-master-SNAPSHOT/clojure-1.8.0-master-SNAPSHOT.jar::clojure/core.clj|5683| clojure.core$load_one.invokeStatic clojure.core$load_one.invoke(core.clj) zipfile:/Users/michael.blume/.m2/repository/org/clojure/clojure/1.8.0-master-SNAPSHOT/clojure-1.8.0-master-SNAPSHOT.jar::clojure/core.clj|5728| clojure.core$load_lib$fn__5618.invoke zipfile:/Users/michael.blume/.m2/repository/org/clojure/clojure/1.8.0-master-SNAPSHOT/clojure-1.8.0-master-SNAPSHOT.jar::clojure/core.clj|5727| clojure.core$load_lib.invokeStatic clojure.core$load_lib.doInvoke(core.clj) || clojure.lang.RestFn.applyTo(RestFn.java:142) zipfile:/Users/michael.blume/.m2/repository/org/clojure/clojure/1.8.0-master-SNAPSHOT/clojure-1.8.0-master-SNAPSHOT.jar::clojure/core.clj|647| clojure.core$apply.invokeStatic zipfile:/Users/michael.blume/.m2/repository/org/clojure/clojure/1.8.0-master-SNAPSHOT/clojure-1.8.0-master-SNAPSHOT.jar::clojure/core.clj|5765| clojure.core$load_libs.invokeStatic clojure.core$load_libs.doInvoke(core.clj) || clojure.lang.RestFn.applyTo(RestFn.java:137) zipfile:/Users/michael.blume/.m2/repository/org/clojure/clojure/1.8.0-master-SNAPSHOT/clojure-1.8.0-master-SNAPSHOT.jar::clojure/core.clj|647| clojure.core$apply.invokeStatic zipfile:/Users/michael.blume/.m2/repository/org/clojure/clojure/1.8.0-master-SNAPSHOT/clojure-1.8.0-master-SNAPSHOT.jar::clojure/core.clj|5787| clojure.core$require.invokeStatic clojure.core$require.doInvoke(core.clj) || clojure.lang.RestFn.invoke(RestFn.java:421) || clojure.tools.analyzer.jvm.utils$eval4392.invokeStatic(form-init8663423518975891793.clj:1) || clojure.tools.analyzer.jvm.utils$eval4392.invoke(form-init8663423518975891793.clj) || clojure.lang.Compiler.eval(Compiler.java:6934) || clojure.lang.Compiler.eval(Compiler.java:6897) zipfile:/Users/michael.blume/.m2/repository/org/clojure/clojure/1.8.0-master-SNAPSHOT/clojure-1.8.0-master-SNAPSHOT.jar::clojure/core.clj|3096| clojure.core$eval.invokeStatic clojure.core$eval.invoke(core.clj) zipfile:/Users/michael.blume/.m2/repository/org/clojure/clojure/1.8.0-master-SNAPSHOT/clojure-1.8.0-master-SNAPSHOT.jar::clojure/main.clj|240| clojure.main$repl$read_eval_print__7404$fn__7407.invoke zipfile:/Users/michael.blume/.m2/repository/org/clojure/clojure/1.8.0-master-SNAPSHOT/clojure-1.8.0-master-SNAPSHOT.jar::clojure/main.clj|240| clojure.main$repl$read_eval_print__7404.invoke zipfile:/Users/michael.blume/.m2/repository/org/clojure/clojure/1.8.0-master-SNAPSHOT/clojure-1.8.0-master-SNAPSHOT.jar::clojure/main.clj|258| clojure.main$repl$fn__7413.invoke zipfile:/Users/michael.blume/.m2/repository/org/clojure/clojure/1.8.0-master-SNAPSHOT/clojure-1.8.0-master-SNAPSHOT.jar::clojure/main.clj|258| clojure.main$repl.invokeStatic clojure.main$repl.doInvoke(main.clj) || clojure.lang.RestFn.invoke(RestFn.java:1523) zipfile:/Users/michael.blume/.m2/repository/org/clojure/tools.nrepl/0.2.10/tools.nrepl-0.2.10.jar::clojure/tools/nrepl/middleware/interruptible_eval.clj|58| clojure.tools.nrepl.middleware.interruptible_eval$evaluate$fn__637.invoke || clojure.lang.AFn.applyToHelper(AFn.java:152) || clojure.lang.AFn.applyTo(AFn.java:144) zipfile:/Users/michael.blume/.m2/repository/org/clojure/clojure/1.8.0-master-SNAPSHOT/clojure-1.8.0-master-SNAPSHOT.jar::clojure/core.clj|645| clojure.core$apply.invokeStatic zipfile:/Users/michael.blume/.m2/repository/org/clojure/clojure/1.8.0-master-SNAPSHOT/clojure-1.8.0-master-SNAPSHOT.jar::clojure/core.clj|1874| clojure.core$with_bindings_STAR_.invokeStatic clojure.core$with_bindings_STAR_.doInvoke(core.clj) || clojure.lang.RestFn.invoke(RestFn.java:425) zipfile:/Users/michael.blume/.m2/repository/org/clojure/tools.nrepl/0.2.10/tools.nrepl-0.2.10.jar::clojure/tools/nrepl/middleware/interruptible_eval.clj|56| clojure.tools.nrepl.middleware.interruptible_eval$evaluate.invokeStatic clojure.tools.nrepl.middleware.interruptible_eval$evaluate.invoke(interruptible_eval.clj) zipfile:/Users/michael.blume/.m2/repository/org/clojure/tools/nrepl/0.2.10/tools.nrepl-0.2.10.jar::clojure/tools/nrepl/middleware/interruptible_eval.clj|191| clojure.tools.nrepl.middleware.interruptible_eval$interruptible_eval$fn__679$fn__682.invoke zipfile:/Users/michael.blume/.m2/repository/org/clojure/tools/nrepl/0.2.10/tools.nrepl-0.2.10.jar::clojure/tools/nrepl/middleware/interruptible_eval.clj|159| clojure.tools.nrepl.middleware.interruptible_eval$run_next$fn__674.invoke || clojure.lang.AFn.run(AFn.java:22) || java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) || java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) || java.lang.Thread.run(Thread.java:745)

我看不出AOT与这个问题有何关联?

0
by

评论者:bronsa

(链接:~michaelblume) 更新的补丁应该可以解决你报告的问题。

0

评论由:michaelblume

太酷了,谢谢 =)

新补丁不再删除未使用的 MethodImplCache,这是故意的吗?

0

评论由:alexmiller

如果在描述中有变更内容的列表会比较酷。例如:“将 MethodImplCache 重命名为 ImplCache”,等等。这有助于更容易地审查。

0

评论者:bronsa

附上了更新的补丁,该补丁不替换 MethodImplCache 为 ImplCache,而是简单地重用 MethodImplCache,减少了补丁的影响,使其更容易(且更安全)进行审查。

0
_评论由:alexmiller_

因为这在新的 {{inst?}}谓词中使用 - 请参阅 https://github.com/clojure/clojure/commit/58227c5de080110cb2ce5bc9f987d995a911b13e
0
_评论由:alexmiller_

我对 v3 补丁的执行前和执行后进行了测试。执行前的时间很接近,但我无法复现执行后的结果。我得到了这个,实际上在未找到的情况下更糟糕


(let [x (x.)] (bench (satisfies? p x))) ;; 执行时间平均值:76.833504 ns
(let [y (y.)] (bench (satisfies? p y))) ;; 执行时间平均值:20.570007 µs

0
_评论由:bronsa_

v4 补丁解决了未找到情况下的回归问题,不确定这是怎么发生的,对此表示歉意。
以下是现在的性能测试结果

clojure master

user=> (let [x (x.)] (bench (satisfies? p x)))
评估次数:604961580 次在60次样本中,每次样本数量为10082693
             执行时间平均值:112.649568 ns
    执行时间标准偏差:12.216782 纳秒
   执行时间下四分位数:99.299203 纳秒 ( 2.5%)
   执行时间上四分位数:144.265205 纳秒 (97.5%)
                   开销使用:1.898271 纳秒

在60个样本中发现了3个异常值(5.0000 %)
   轻度严重     2 (3.3333 %)
   轻度轻微     1 (1.6667 %)
 异常值引起的变化:73.7545 % 异常值严重拉高了方差
nil




user=> (let [y (y.)] (bench (satisfies? p y)))
评估次数:22676100 在60个样本中,共377935次调用。
             执行时间平均值:2.605426 微秒
    执行时间标准偏差:141.100070 纳秒
   执行时间下四分位数:2.487234 微秒 ( 2.5%)
   执行时间上四分位数:2.873045 微秒 (97.5%)
                   开销使用:1.898271 纳秒

在60个样本中发现了1个异常值(1.6667 %)
   轻度严重     1 (1.6667 %)
 异常值引起的变化:40.1251 % 异常值适当地拉高了方差
nil



master + v4

user=> (let [x (x.)] (bench (satisfies? p x)))
评估次数:731759100 在60个样本中,共12195985次调用。
             执行时间平均值:79.321426 纳秒
    执行时间标准偏差:3.959245 纳秒
   执行时间下四分位数:75.365187 纳秒 ( 2.5%)
   执行时间上四分位数:87.986479 纳秒 (97.5%)
                   开销使用:1.905711 纳秒

在60个样本中发现了1个异常值(1.6667 %)
   轻度严重     1 (1.6667 %)
 异常值引起的变化:35.2614 % 异常值适当地拉高了方差
nil



user=> (let [y (y.)] (bench (satisfies? p y)))
评估次数:771220140 在60个样本中,共12853669次调用。
             执行时间平均值:77.410858 纳秒
    执行时间标准偏差:1.407926 纳秒
   执行时间下四分位数:75.852530 纳秒 ( 2.5%)
   执行时间上四分位数:80.759226 纳秒 (97.5%)
                   开销使用:1.897646 纳秒

在60个样本中发现了4个异常值(6.6667 %)
   轻度严重     3 (5.0000 %)
   轻度轻微     1 (1.6667 %)
 异常值引起的变化:7.7866 % 异常值轻微拉高了方差


总结一下
master found = 112ns
master not-found = 2.6us

master+v4 found = 79ns
master+v4 not-found = 77ns
0

评论由:michaelblume

对于已经使用了特定协议实现声明的记录,并且因此实现了相应的接口,使用对该接口的(实例?)检查作为快速路径是否有意义?

0

评论由:alexmiller

Michael - 那个检查已经包含在内了

Nicola - 我有一些评论/问题

  1. 我不明白NIL东西的目的 - 你能解释一下吗?
  2. 当x是接口的一个实例时,旧代码返回x,而新的find-protocol-impl*代码在find-protocol-impl*中返回接口。为什么这样改变?
  3. 这个:`(alter-var-root (:var protocol) assoc :impl-cache (expand-method-impl-cache cache c impl))` 在我的理解中不是线程安全的 - 我认为两个不同线程对不同的实现同时发生缺失会导致缓存中只会有一个。这可能是极不可能的,而且可能不会造成太大的影响,因为缓存将在下一次调用时更新(不会给出错误答案),但我想提一下。我觉得没有简单的方法可以避免它而不用做大量修改。
0

评论者:bronsa

亚历克斯,感谢你查看这个。
1- NIL 对象在方法实现缓存中是 nil 的占位符,因为 find-and-cache-protocol-impl 测试 nil? 来确定是否已缓存了分派

2- 这个更改纯粹是为了保持一致性,使得 find-and-cache-protocol-impl 总是返回一个类或接口,而不是类或接口以及具体实例。在行为上,这不会导致任何变化,因为 find-protocol-impl 的两个消费者——即 find-protocol-methodsatisfies?——在这种情况下都不关心该值是什么

3- 你是对的,这确实不是线程安全的,但是我认为这是一个合理的折衷,因为它不会导致任何错误行为,最坏的情况只会导致额外的缓存缺失,而使它线程安全则意味着在每次缓存命中或未命中时都会带来额外的性能损失

0

评论由:michaelblume

(链接: ~bronsa) 我看到从这个补丁中行为发生了变化

`
(defprotocol BoolProtocol
(proto-fn [this]))

(extend-protocol BoolProtocol
Object
(proto-fn [x] "Object impl")

nil
(proto-fn [x] "Nil impl"))

(proto-fn false)
`

在 Clojure master 中返回 "Object impl",使用这个补丁则返回 "Nil impl"

...