请在2024年度Clojure调查中分享您的想法!

欢迎!请查看关于页面获取有关此信息的更多信息。

0
Clojure

摘要

locking 宏比Java的synchronized块慢2倍,尽管locking 宏所做的只是在生成monitorentermonitorexit指令。

使用Java的synchronized块而不是直接生成monitorentermonitorexit指令可以解决这个问题。

开发邮件列表中的线程是:https://groups.google.com/forum/#!topic/clojure-dev/dJZtRsfikXU

相关于

CLJ-1472与这个工单有关。CLJ-1472的目的与本工单不同,但解决每个问题所需的所有更改都相同。因此,这两个工单的补丁内容几乎相同。

BENCHMARKS

我为验证这个问题创建了两个示例程序,在这些程序中,我创建了许多线程,并从线程中更新Map,以制造高度竞争的条件。

在Java示例中,我使用简单的Thread和synchronized块在一个HashMap上。

在Clojure示例中,我创建了两个示例。
在第一个示例中,我使用atom来保留一个关联(Map),并使用swap!assoc来更新这个关联。
在第二个示例中,我使用volatile!来保留一个java.util.HashMap,并在更新HashMap之前在volatile引用上使用locking

Java示例
https://github.com/tyano/MultithreadPerformance

Clojure示例
https://github.com/tyano/clj-multithread-performance

{quote}
上面描述了运行程序的说明。
{quote}

我的机器上的结果(macos 10.13.1,Java 1.8.0_144,3.1 GHz Intel Core i5,Clojure 1.9.0)

A. Java示例:6,006ms
B. Clojure - 使用关联的atom:18,984ms
C. Clojure - 在HashMap上使用锁定:15,883ms

B(Clojure的Atom和swap!)比Java慢,但我理解原因。更新一个关联会创建一个新的对象。当然,它使用PersistentMap,所以它的性能应该比创建一个完整Map实例的副本要好,但它会比直接更新一个简单的java.util.Map实例(比如Java示例那样)慢。而且swap!可能会重复尝试更新操作。所以这个结果对我来说是可以理解的。

但我觉得C(在HashMap上锁定)的结果(15,883ms)太快了。

locking 宏只是生成monitorentermonitorexit指令,它几乎与synchronized块所做的相同,所以结果应该接近Java示例的结果(6,006ms)。

调查

我怀疑locking生成的字节码与synchronized生成的不同。
工单CLJ-1472也引导我的怀疑。这个工单表明,locking生成的字节码与JDK生成的不同。

我的怀疑:《代码锁定》(locking)将生成不同的字节码,而Java运行时无法很好地优化生成的字节码。
直接生成操作码相反,如果locking宏将宏主体包装在一个Fn中,并且仅调用一个Java方法,该方法在同步块中调用的提供Fn,运行时可能能够很好地优化代码?

我在locking宏上创建了一个这样的补丁(在该票上附加),并尝试用修补后的clojure再次进行了一个示例C。

结果是:6,988毫秒

总结

当前locking宏的实现存在性能问题。生成的locking字节码在Java运行时不会得到很好的优化。因此,性能比Java中的synchronized块快2倍。

该票上附加的补丁可以解决这个问题。

1 答案

0
by
...