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

欢迎!请查看 关于 页面以了解更多关于此功能的信息。

0
Clojure

目前 {{Reflector.getMethods}} 执行了包括 {{java.lang.reflect.Method}} 复制的昂贵逻辑。
查看: https://github.com/clojure/clojure/blob/b8607d5870202034679cda50ec390426827ff692/src/jvm/clojure/lang/Reflector.java#L373

在我应用程序中,我看到以下堆栈跟踪

在 Reflector.copyMethods 中,在 Reflector.invokeInstanceMethod 中...

此类堆栈跟踪是所有堆分配的次顶级消费者。

JDK 不能缓存 {{Methods}} / {{Fields}},因为它们是可变的(例如,用户可以在任何地方调用 {{setAccessible}})。
然而,对于 Clojure 的目的,我认为缓存 {{Methods}} 和 {{Fields}} 是可行的。

您怎么看?
例如 WeakHashMap>> 或更复杂的结构以考虑 {{String name}}。

15 个答案

0

由 alexmiller 发布的评论:

如果您在应用程序中将 Reflector 视为热点,您可能需要启动 * } 并使用类型提示来避免反射。

0
由 vladimirsitnikov 发布的评论:

您的意思是 Clojure 中永远没有理由使用反射吗?
我确实明白,如果开发人员提供了足够的类型提示,反射将消失。

然而
1) 我不知道这是否容易实现(换句话说,如果它可能,可维护性等)
2) 我不确定“总是使用类型提示”是否被认为是最佳实践。例如,[warn-on-reflection|https://docs.clojure.org/clojure.core/*warn-on-reflection* 的文档页面没有说“始终使用类型提示”
缓存 {{copyMethods}} 似乎在这里是一棵低垂的果实,因此它可以节省那些省略了类型提示的人的 CPU 周期。

0

由 alexmiller 发布的评论:

0

评论者:mikera

简单缓存Reflector.getMethods调用的补丁

0

评论者:mikera

;; clojure 1.8.0-RC3
user=> (let (link: v (identity 1)) (time (dotimes (link: i 1000) (.doubleValue v))))
"Elapsed time: 1.598779 msecs"

;; with cached arities
user=> (let (link: v (identity 1)) (time (dotimes (link: i 1000) (.doubleValue v))))
"Elapsed time: 1.359888 msecs"

0

评论者:vladimirsitnikov

(link: ~mikera),我想知道您的补丁是否会导致并发工作负载的性能回归。

您创建了一个争议点,因为许多线程将尝试更新{{私有静态InstanceMethodCache[] instanceMethodCache}}条目,因此将同时遇到“真正共享”和“伪共享”问题。

InstanceMethodCache应该声明为final并将首字母大写吗?

0

由 alexmiller 发布的评论:

这个工单需要一个更好的问题定义。即:“我在做____”(附上示例),其中显示了Reflector.getMethods()是瓶颈。

如果我对问题进行猜测,我仍然不相信这是最佳解决方案。

ThreadLocal可能是具有最低并发影响的最优缓存解决方案。

0

评论者:mikera

这不应该有任何明显的并发影响:这个非常简单的方法不需要任何锁定。大多数时间只是一个对堆上数组的无锁读取,Java内存模型足以保证其正确行为。这比threadlocal更便宜,例如,这里有一些证据表明这快10-20倍:http://stackoverflow.com/questions/609826/performance-of-threadlocal-variable

至少,任何并发影响都非常微小,将远远小于经常避免昂贵的getMethods调用的好处。数组访问的成本几分钟纳秒,而getMethods的成本从上面的基准测试来看是几分钟纳秒。

我能想到的最糟糕的并发情况是两个不同的线程以高频率调用不同方法上的getMethods,并且这些调用是完美交错的,以便它们始终使缓存无效。但即使在这种情况下,这也许并不比当前的代码更糟。

@Vladimir 是的,instanceMethodCache可以是final。我猜这可能会微幅帮助JVM。

@Alex,我提出这个补丁是因为它与目前的实现相比是改进的,我当然不认为它是“最佳解决方案”。本着开源和渐进改进的精神,我希望您考虑接受它,即使这个问题仍然可以留待将来考虑。这也与clj-1866有关,我正在尝试通过几种不同的方式提高“快速路径”的反射性能。如果您希望有一个包含大量改进的单个大补丁,我当然可以这样做。我听说更小、更“明显”的补丁更容易进行审查,但我愿意这样做。

0

评论者:vladimirsitnikov

(链接: ~mikera),你的观点不正确。

请在此处查看:http://shipilev.net/blog/2014/jmm-pragmatics/#_benchmarks,幻灯片77/100 "SC-DRF: Writes"

{quote}Alexey Shipilev:这强化了首先应避免数据共享而不是volatile的想法{quote}

拥有 {{ThreadLocal}} 缓存可以消除“共享更新”问题。

{quote}这个问题条目需要更好的问题定义。即:“我在做____”(附上一个例子),这显示了 Reflector.getMethods() 是瓶颈{quote}
这是正确的。
我的特殊情况已经被开发团队解决了。
我只认为一些基本的缓存可以让 Clojure 默认做正确的事情,并减少手动编写的类型专业化。

0

由 alexmiller 发布的评论:

我看到了很多“应该”类型的陈述。整个问题的关键是,除非我们 知道 没有影响,否则这类更改不会进入。但更重要的是,我不会将其标记为已_triage,除非它是一个从问题陈述开始的优秀票档。

0

评论者:mikera

Alex,你说的“知道没有影响”是什么意思?这似乎是一个无稽之谈的立场,如果您在提高快速路径/常见情况的同时允许罕见的角落案例出现轻微的退化,这是一个完全可以接受的权衡。

此外,这绝对不是 Clojure 更改的普遍标准。很多变化都进入了 Clojure,这会导致其他领域的性能退化,您只需查看 Andy Fingerhut 优秀的基准测试工作即可看到这一点:https://jafingerhut.github.io/clojure-benchmarks-results/Clojure-expression-benchmark-graphs.html

就性能相关问题而言,在我看来,问题声明是明显的:“X 的性能不理想,这对正在做 X 的用户造成了伤害。”如果您想要一个新票档/更改描述,说明类似的内容,我会很乐意这样做,但这只是感觉像是一种官僚主义的框子勾选。请将此作为对您贡献过程的宝贵反馈考虑。

您究竟需要看到哪些(即,哪些基准)作为演示此类性能相关问题改进的有效证明?

0

由 alexmiller 发布的评论:

与我关于 CLJ-1866 的评论类似,本票档的标题是“Reflector.getMethods 应该被缓存”。这又是一个解决方案,而不是问题。我寻找的是一个像“循环中的重复反射很慢”这样的标题,以及以一些示例代码开始说明问题的描述。在没有良好的问题陈述的情况下,我无法对票档 triage。我仍然可能会考虑问题的重要程度足够低,以至于现在没有 triage 的价值 - 但我会保持判断到票档得到改进。

先前变更对性能产生了预料之外的影响,这为我提出的建议增加了额外的可信度,即这个(以性能为中心的)工单应该验证其主张。你添加了代码,这使得该代码的“未命中”路径比之前慢。慢多少?它应该使“命中”路径更快——快多少?在典型代码中,我们多久遇到一次命中与未命中路径?我的假设是示例将证明一个命中路径常见的案例。作为筛选员,我必须提出这些问题以评估任何提出的解决方案。

此外,你正在引入并发问题,并且需要进行更多工作以验证正确性(当前补丁存在可见性问题)以及你没有引入竞争或内存问题。这些是任何缓存相关的优化的典型问题,我可以指向许多以前处理过这些问题的工单。

0

评论者:mikera

感谢Alex解释你的担忧。

我同意以问题为导向的方法来处理补丁,所以我建议以下内容:
- 我们关闭此问题以及clj-1866
- 我将创建一个专门针对反射性能的问题工单
- 我将针对多个不同案例对更改进行基准测试
- 你将假设我们可以证明在常见情况中可以观察到明显的改进,所有测试都像以前一样通过,并且在边缘情况下没有出现严重的回归(并发访问、频繁的缓存未命中等。)

如果你想一个问题导向的问题,我认为没有必要为每个“解决方案”创建单独的工单/补丁(尽管一些开源项目选择这样做,但它们通常有一个更加简化的流程来处理小的更改/优化,这可能不适合Clojure的开发流程)

同意吗?

0

由 alexmiller 发布的评论:

正如我说的,我们更喜欢小而专注的工单和补丁,而不是一个大补丁。

我会反复强调,如果在某个场景中类型提示可以解决这个问题,那么我不会认为做任何这项工作是有意义的。

0
参考:https://clojure.atlassian.net/browse/CLJ-1784(由alex+import报告)
...