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

欢迎!请参阅 关于 页面以获取更多关于如何使用此内容的详细信息。

+9
Java Interop
Clojure 的随机函数目前使用 {{Math.random}} 和相关功能,这使得它们无法进行种子化。这似乎是使用动态 var (与额外的参数相比) 的一个恰当的使用场景,因为希望随机行为的库代码可以毫无额外努力地透明地支持种子化。

我在 {{clojure.core}} 中提出 {{(def ^:dynamic \*rand* (java.util.Random.))}},并将 {{rand}}、{{rand-int}}、{{rand-nth}} 和 {{shuffle}} 更新为使用 {{\*rand*}}。

我认为在语义上这不会是一个破坏性的更改。

b2. Criterium 基准测试

我进行了一些基准测试,以尝试了解使用动态 var 的性能影响,以及测量对并发访问的改变。

使用的代码位于 [https://github.com/gfredericks/clj-1452-tests]; 原始输出生在注释中。

{{rand}} 略慢,而 {{shuffle}} 略快。使用 {{shuffle}} 从 8 个线程开始,几乎没变慢,但将补丁版本的代码手动切换到 {{ThreadLocalRandom}} 可以达到 2.5 倍的速度提升。

在我的 8 核心 Linode 虚拟机上运行

||基准测试||Clojure||运行时间平均值||运行时间标准差||
|{{rand}}|1.6.0|61.3ns|7.06ns|
|{{rand}}|1.6.0 + {{\*rand\*}}|63.7ns|1.80ns|
|{{shuffle}}|1.6.0|12.9µs|251ns|
|{{shuffle}}|1.6.0 + {{\*rand\*}}|12.8µs|241ns|
|{{threaded-shuffling}}|1.6.0|151ms|2.31ms|
|{{threaded-shuffling}}|1.6.0 + {{\*rand\*}}|152ms|8.77ms|
|{{threaded-local-shuffling}}|1.6.0|N/A|N/A|
|{{threaded-local-shuffling}}|1.6.0 + {{\*rand\*}}|64.5ms|1.41ms|


*方法:* 创建动态 var \*rand* 并更新 {{rand}}、{{rand-int}}、{{rand-nth}} 和 {{shuffle}} 以使用 {{\*rand*}}

*补丁:* CLJ-1452.patch

*审查人员:*

11 个答案

0

评论者:gfredericks

附件 CLJ-1452.patch,包含与基准测试相同的代码。

0

评论者:gfredericks

我们在摆弄这些东西的时候,是否应该让Clojure的随机函数默认是线程局的?我们可以创建一个具有线程局逻辑的自定义{{Random}}子类(由于Java 6,避免使用{{ThreadLocalRandom}}),并将其设置为*的默认值。

0

评论由:alexmiller 发布

我认为线程局问题是有趣的,但不确定答案。

如果在描述中用表来总结测试结果,将被认为是件好事,并且标准输出在评论中。

0
_评论由:gfredericks_ 发布

测试仓库的完整输出(描述中的表已总结)


$ echo "Clojure 1.6.0"; lein with-profile +clj-1.6 run; echo "Clojure 1.6.0 with *rand*"; lein with-profile +clj-1452 run
Clojure 1.6.0

;;;;;;;;;;;;;;;;;;
;; 测试rand ;;
;;;;;;;;;;;;;;;;;;
WARNING: Final GC required 1.261632096547911 % of runtime
评估计数:644646900在60个包含10744115次的样本中。
            执行时间平均:61.297605纳秒
   执行时间标准偏差:7.057249纳秒
   执行时间下四分位数:56.872437纳秒(2.5%)
   执行时间上四分位数:84.483045纳秒(97.5%)
   Overhead used : 16.319772 ns

在60个样本中发现6个异常值(10.0000%)
   low-severe   1 (1.6667 %)
   low-mild   5 (8.3333 %)
 Variance from outliers : 75.5119 % Variance is severely inflated by outliers

;;;;;;;;;;;;;;;;;;;;;
;; 测试shuffle ;;
;;;;;;;;;;;;;;;;;;;;;
评估计数:4780800在60个包含79680次的样本中。
             执行时间平均:12.873832微秒
    执行时间标准偏差:251.388257纳秒
   执行时间下四分位数:12.526871微秒(2.5%)
   执行时间上四分位数:13.417559微秒(97.5%)
   Overhead used : 16.319772 ns

在60个样本中发现3个异常值(5.0000%)
   low-severe   3 (5.0000 %)
 Variance from outliers : 7.8591 % Variance is slightly inflated by outliers

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 测试threaded-shuffling ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
评估计数:420在60个包含7次的样本中。
             执行时间平均:150.863290毫秒
    执行时间标准偏差:2.313755毫秒
   执行时间下四分位数:146.621548毫秒(2.5%)
   执行时间上四分位数:155.218897毫秒(97.5%)
   Overhead used : 16.319772 ns
Clojure 1.6.0 with *rand*

;;;;;;;;;;;;;;;;;;
;; 测试rand ;;
;;;;;;;;;;;;;;;;;;
评估次数:781707720,来自13028462次调用的60个样本。
             平均执行时间:63.679152纳秒
    执行时间的标准偏差:1.798245纳秒
   执行时间下四分位数:61.414851纳秒 (2.5%)
   执行时间上四分位数:67.412204纳秒 (97.5%)
                 使用的额外开销:13.008428纳秒

在60个样本中发现3个异常值(5.0000%)
   low-severe   3 (5.0000 %)
 离群值方差:15.7596%,方差受到离群值的适度影响

;;;;;;;;;;;;;;;;;;;;;
;; 测试shuffle ;;
;;;;;;;;;;;;;;;;;;;;;
评估次数:4757940,来自79299次调用的60个样本。
             平均执行时间:12.780391微秒
    执行时间的标准偏差:240.542151纳秒
   执行时间下四分位数:12.450218微秒 (2.5%)
   执行时间上四分位数:13.286910微秒 (97.5%)
                 使用的额外开销:13.008428纳秒

在60个样本中找到1个离群值(1.6667%)
   low-severe   1 (1.6667 %)
 离群值方差:7.8228%,方差稍微受到离群值的影响

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 测试threaded-shuffling ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
评估计数:420在60个包含7次的样本中。
             平均执行时间:152.471310毫秒
    执行时间的标准偏差:8.769236毫秒
   执行时间下四分位数:147.954789毫秒 (2.5%)
   执行时间上四分位数:161.277200毫秒 (97.5%)
                 使用的额外开销:13.008428纳秒

在60个样本中发现3个异常值(5.0000%)
   low-severe   3 (5.0000 %)
 离群值方差:43.4058%,方差受到离群值的适度影响

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 测试线程局部洗牌 ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
评估次数:960,来自16次调用的60个样本。
             平均执行时间:64.462853毫秒
    执行时间的标准偏差:1.407808毫秒
   执行时间下四分位数:62.353265毫秒 (2.5%)
   执行时间上四分位数:67.197368毫秒 (97.5%)
                 使用的额外开销:13.008428纳秒

在60个样本中找到1个离群值(1.6667%)
   low-severe   1 (1.6667 %)
 离群值方差:9.4540%,方差稍微受到离群值的影响
0

评论者:gfredericks

我认为使用一个{{ThreadLocal}}与添加 }在逻辑上是独立的,因此它可以是单独的工单。我只是在这里提了一下,因为这可能会减少任何由 }带来的任何减速,但现在我再看看基准结果,减速可能并不重要。

0

评论者:gfredericks

还值得注意,正如我在基准代码中所做的那样,仅通过补丁的更改(即没有涉及ThreadLocal)用户仍然能够手动进行ThreadLocal,这目前在技术上是不可能的。

0

评论者:stu

解决方案:(链接:https://github.com/clojure/data.generators 文字:data.generators) 提供可设置种子的随机数

0

评论人:gshayban

注意,ThreadLocalRandom从JDK 7开始可用。

0

评论者:gfredericks

ThreadLocalRandom的JDK7相关问题不再成为问题,对吗?

0

评论由:alexmiller 发布

0
参考:https://clojure.atlassian.net/browse/CLJ-1452(由gfredericks报告)
...