请在 Clojure 2024年调查问卷!中分享您的想法。

欢迎!有关如何使用本网站的更多信息,请参阅关于页面。

0
Clojure

摘要

locking宏比Java中的synchronized块慢了两倍,尽管locking宏所做的事情仅仅是生成monitorentermonitorexit操作码。

这个问题可以通过使用Java中的synchronized块而不是直接生成monitorentermonitorexit来解决。

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

相关内容

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

性能测试

我为验证此问题制作了两个示例程序,这些程序创建了多个线程,并通过线程更新Map来人为地制造高度竞争条件。

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

在Clojure示例中,我制作了两个示例。
在第一个示例中,我使用atom来保存关联(Map),并使用swap!assoc来更新关联。
在第二个示例中,我使用volatile!来保存一个java.util.HashMap,并在更新HashMap之前使用locking对volatile引用进行锁定。

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生成的不同。

我的怀疑是:locking将生成不同的字节码,并且Java运行时无法很好地优化生成的字节码。
不直接生成操作码(opcodes),如果 locking 宏将宏体包装到一个函数(Fn)中,并仅仅调用一个 Java 方法,该方法以同步块的方式调用提供的 Fn,那么运行时可能会对代码进行很好的优化吗?

我在 locking 宏(在此票据中附带)上做了一个这样的补丁,并使用修补过的 clojure 再次尝试了一个 C 样本。

结果是:6,988毫秒

摘要

《code>locking 宏的当前实现存在性能问题。生成的 locking 宏的字节码在 Java 运行时上不会很好地被优化。因此,性能比 Java 的 synchronized 块慢 2倍。

在此票据中附带的补丁可以解决这个问题。

1 答案

0
...