最近在进行性能优化工作时,我发现展开成单独的 assoc 调用比使用多个键要快得多(针对我的特定应用程序,快了10%)。随后 Zachary Tellman 指出,clojure.core 完全没有对 assoc 进行展开,即使是相对较少的键数。
我们已经对其他通过 apply
调用的性能关键函数进行了展开,例如 update
https://github.com/clojure/clojure/blob/master/src/clj/clojure/core.clj#L5914,但是我觉得对于许多应用程序和库来说,assoc
(位于关键路径上)很可能会从中受益。
我还没有为这个特性开发补丁,但我进行了一些独立的基准测试
https://github.com/yeller/unrolling-assoc-benchmarks
基准测试结果
代码: https://github.com/yeller/unrolling-assoc-benchmarks/blob/master/src/bench_assoc_unrolling.clj
| |1 |2 |3 |4 |
| :-- | :-- | :-- | :-- | :-- |
| 空数组映射(未展开) | 23ns | 93ns | 156ns | 224ns |
| 空数组映射(展开 assoc) | N/A | 51ns | 80ns | 110ns |
| | | | | |
| 20个元素的持久哈希映射(未展开) | 190ns | 313ns | 551ns | 651ns |
| 20个元素的持久哈希映射(展开 assoc) | N/A | 250ns | 433ns | 524ns |
| | | | | |
| 记录(未展开) | 12ns | 72ns | 105ns | 182ns |
| 记录(展开 assoc) | N/A | 21ns | 28ns | 41ns |
每项测量都是在独立的 JVM 中进行的,以避免 JIT 路径依赖。
基准测试在一台商品服务器(8个 CPU,32GB 内存)上运行,使用 ubuntu 12.04 和 Java 8 的最新版本。附上了 cpuinfo
、uname
和 java -version
的输出。
启用了相对标准的 JVM 生产标志,并小心地禁用了 leiningen 的启动时间优化(该优化将禁用许多 JIT 优化)。
可以通过克隆仓库并运行 script/bench
来运行基准测试。
对于这个补丁有一个悬而未决的问题:我们应将调用展开到什么程度?在 1.7 零版本中已展开的 update
展开到 3 个参数。添加更多的展开并不困难,但这会影响 assoc 的可读性。
补丁: CLJ-1656-v5.patch