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

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

+9 投票
Java 互操作
Clojure 的随机函数目前使用 {{Math.random}} 和相关特性,这使得它们无法进行种子化。这似乎是一个适当的动态 var 的应用(与额外的参数相比),因为希望行为随机的库代码可以透明地支持种子化,而无需任何额外的努力。

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

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

### 评测基准

我进行了一些基准测试,试图了解使用动态 var 的性能影响,以及同时访问的变化。

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

{{rand}} 稍慢,而 {{shuffle}} 则略快。使用来自 8 个线程的 {{shuffle}} 略慢,但在修补版中手动切换到 {{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}}子类,在其中包含{{ThreadLocal}}逻辑(避免{{ThreadLocalRandom}}因为Java 6),并将其设置为*的默认值。

0 投票

评论者:alexmiller

我认为ThreadLocal问题是很有趣的,但不确定答案。

如果描述以表格总结了测试结果,并在注释中输出准则,那将会很方便。

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 ;;
;;;;;;;;;;;;;;;;;;
警告:最终GC需要运行时的1.261632096547911 %
评估次数:在60个样本中为644646900,每个样本为10744115次调用。
             平均执行时间:61.297605 ns
    执行时间标准差:7.057249 ns
   执行时间下四分位数:56.872437 ns ( 2.5%)
   执行时间上四分位数:84.483045 ns (97.5%)
                   开销使用:16.319772 ns

在60个样本中发现了6个异常值(10.0000%)
    低严重   1 (1.6667 %)
    低轻微     5 (8.3333 %)
 从异常值中得出的方差:75.5119 % 异常值严重增加了方差

;;;;;;;;;;;;;;;;;;;;;
;; 测试shuffle ;;
;;;;;;;;;;;;;;;;;;;;;
评估次数:4780800在60个样本中,每个样本为79680次调用。
             平均执行时间:12.873832 µs
    执行时间标准差:251.388257 ns
   执行时间下四分位数:12.526871 µs ( 2.5%)
   执行时间上四分位数:13.417559 µs (97.5%)
                   开销使用:16.319772 ns

在60个样本中发现了3个异常值(5.0000%)
    低严重   3 (5.0000%)
 从异常值中得出的方差:7.8591 % 异常值稍微增加了方差

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 测试threaded-shuffling ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
评估次数:420在60个样本中,每个样本为7次调用。
             平均执行时间:150.863290 ms
    执行时间标准差:2.313755 ms
   执行时间下四分位数:146.621548 ms ( 2.5%)
   执行时间上四分位:155.218897 毫秒(97.5%)
                   开销使用:16.319772 ns
Clojure 1.6.0 与 *rand*

;;;;;;;;;;;;;;;;;;
;; 测试rand ;;
;;;;;;;;;;;;;;;;;;
评估次数:781707720,分布在 60 个大小为 13028462 的样本中。
             执行时间平均值:63.679152 纳秒
    执行时间标准差:1.798245 纳秒
   执行时间下四分位:61.414851 纳秒( 2.5%)
   执行时间上四分位:67.412204 纳秒(97.5%)
                   开销使用:13.008428 纳秒

在60个样本中发现了3个异常值(5.0000%)
    低严重   3 (5.0000%)
 异常值引起的方差:15.7596 % 方差因异常值而中度膨胀

;;;;;;;;;;;;;;;;;;;;;
;; 测试shuffle ;;
;;;;;;;;;;;;;;;;;;;;;
评估次数:4757940,分布在 60 个大小为 79299 的样本中。
             执行时间平均值:12.780391 微秒
    执行时间标准差:240.542151 纳秒
   执行时间下四分位:12.450218 微秒( 2.5%)
   执行时间上四分位:13.286910 微秒(97.5%)
                   开销使用:13.008428 纳秒

在 60 个样本中发现了 1 个异常值(1.6667 %)
    低严重   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%)
    低严重   3 (5.0000%)
 异常值引起的方差:43.4058 % 方差因异常值而中度膨胀

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 测试线程局部打乱 ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
评估次数:960,分布在 60 个大小为 16 的调用样本中。
             执行时间平均值:64.462853 毫秒
    执行时间标准差:1.407808 毫秒
   执行时间下四分位:62.353265 毫秒( 2.5%)
   执行时间上四分位:67.197368 毫秒(97.5%)
                   开销使用:13.008428 纳秒

在 60 个样本中发现了 1 个异常值(1.6667 %)
    低严重   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}} 的 JDK 7+ 方面不再有问题,对吗?

0 投票

评论者:alexmiller

是的

0 投票
引用: https://clojure.atlassian.net/browse/CLJ-1452 (由 gfredericks 报告)
...