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 ns
(let [x (x.)] (bench (satisfies? p x)));; 执行时间平均值:112.649568 ns
(let [y (y.)] (bench (satisfies? p y)));; 执行时间平均值:2.605426 µs


"原因:" `satisfies?` 通过调用 `find-protocol-impl` 来判断一个对象是否实现了协议,这会检查 x 是否是协议接口的实例或者 x 的类是否是协议实现之一(或者其继承链中存在实现)。这个检查相当昂贵并且没有缓存。

"建议:" 将协议的方法实现缓存扩展,以便处理及缓存实例检查(包括负面结果)。

打补丁后

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


"补丁:" CLJ-1814-v7.patch (依赖于CLJ-2426)

25 条回答

0

评论由:michaelblume 发布

很好。改写 honeysql 之前,它80-90%的时间都花在 satisfies? 调用上。

0

评论由:michaelblume 发布

我明白这是一个难以复现的深层bug,但当我克隆 core.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/clojure/1.8.0-master-SNAPSHOT/clojure-1.8.0-master-SNAPSHOT.jar::clojure/core.clj|5883| clojure.core$load$fn5669.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)
src/main/clojure/clojure/core/match.clj|1| clojure.core.match$eval4960$loading5561auto4961.invoke
src/main/clojure/clojure/core/match.clj|1| clojure.core.match$eval4960.invokeStatic
|| clojure.core.match$eval4960.invoke(match.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$fn5669.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/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.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)
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_print7404$fn7407.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_print7404.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$fn7413.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$fn637.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$fn679$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)

`

在与依赖于clojure.core.match的自己的项目重新加载namespace时遇到相同情况

0
by

由:bronsa发表的评论

是否涉及到AOT(提前编译)?

0

评论由:michaelblume 发布

稍微缩小了搜索范围,如果我查看 tools.analyzer.jvm,打开(Parser),执行 (require 'clojure.tools.analyzer.jvm.utils) 我会得到:

|| java.lang.ClassCastException: java.lang.Class cannot be cast to clojure.asm.Type, compiling:(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(Ahead-of-Time 编译)的涉及呢?

0

由:bronsa发表的评论

(链接: ~michaelblume) 更新的补丁应能修复您报告的问题

0

评论由:michaelblume 发布

太好了,谢谢 =)

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

0

评论者:alexmiller

如果在描述中有改动项的列表,那就太好了。例如:“更名为 MethodImplCache 为 ImplCache”,这有助于更轻松地进行审查。

0

由:bronsa发表的评论

附件中的补丁不替换 MethodImplCache,而是简单地重新使用 MethodImplCache,从而降低了补丁的影响,使审查(和安全)变得更加容易。

0
_评论者:alexmiller_

由于这项技术用在新 {{inst?}} 谓词中,因此提高优先级 - 请参阅 https://github.com/clojure/clojure/commit/58227c5de080110cb2ce5bc9f987d995a911b13e
0
_评论者:alexmiller_

我在 v3 补丁之前和之后运行了测试。在测试之前,时间相近,但无法复制之后的测试结果。我得到了这个结果,实际上在未找到的情况下更糟糕:


(let [x (x.)] (bench (satisfies? p x))) ;; 执行时间平均值:76.833504纳秒
(let [y (y.)] (bench (satisfies? p y))) ;; 执行时间平均值:20.570007微秒

0
评论由:bronsa_ 提供

v4 补丁修复了找不到情况下的回归问题,不清楚怎么会发生这种情况,对此表示歉意。
以下是我现在得到的计时结果

clojure master

user=> (let [x (x.)] (bench (satisfies? p x)))
评估次数:604961580 次在60个样本中,每个样本1082693次调用。
             平均执行时间:112.649568 纳秒
    执行时间标准差: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*代码返回接口。为什么有这个变化?
  3. 这个:`(alter-var-root (:var protocol) assoc :impl-cache (expand-method-impl-cache cache c impl))` 并非线程安全(据我所知)- 我认为两个不同线程上的不同实现在同时缺失时会导致缓存只能有一个。这可能是很少发生的情况,可能不是什么大问题,因为缓存将在下一次调用(不会给出错误答案)时更新,但想提一下。我看不出有什么简单的方法可以避免它,而不需要进行很多修改。
0

由:bronsa发表的评论

Alex,感谢你查看这个
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)我注意到这个补丁导致了行为的变化

`
(定义协议BoolProtocol
(协议函数[这个])

(扩展协议BoolProtocol
对象
(协议函数[x] "对象实现")

nil
(协议函数[x] "nil实现")

(bool false)
`

使用Clojure master返回"对象实现",使用这个补丁返回"nil实现")

...