在调用 {{reduce}} 对 {{ArrayChunk}} 而不是当前的 {{dotimes}} 时,分块序列处理显示出普遍的改进。受影响的功能有 {{map}}、{{filter}} 和 {{keep}}。以下表格展示了相关基准测试数据(单位:毫秒)。括号内的数字比原始数值差(尽管差距很小)。在长范围内整体改进最为显著。
**长范围**
|| f || before(doall)|| after(doall)|| before(chunk-last)|| after(chunk-last)||
| {{(map inc lr)}} | 3.75 | 2.88 | 2.15 | 2.06 |
| {{(keep identity lr)}}| 2.56 | 2.16 | 0.75 | 0.72 |
| {{(filter odd? lr)}} | 2.77 | 2.20 | 1.53 | 1.45 |
**范围**
|| f || before(doall)|| after(doall)|| before(chunk-last)|| after(chunk-last)||
| {{(map inc lr)}} | 3.64 | [3.70] | 2.32 | [2.50] |
| {{(keep identity lr)}}| 2.10 | 1.94 | 0.56 | 0.46 |
| {{(filter odd? lr)}} | 1.95 | [1.99] | 1.19 | [1.66] |
**向量**
|| f || before(doall)|| after(doall)|| before(chunk-last)|| after(chunk-last)||
| {{(map inc lr)}} | 3.81 | 3.68 | 2.44 | 2.15 |
| {{(keep identity lr)}}| 2.03 | [2.16] | 0.53 | 0.46 |
| {{(filter odd? lr)}} | 2.08 | [2.82] | 1.67 | 1.39 |
**gvec**
|| f || before(doall)|| after(doall)|| before(chunk-last)|| after(chunk-last)||
| {{(map inc lr)}} | 3.69 | [3.83] | 1.46 | 1.35 |
| {{(keep identity lr)}}| 2.86 | 2.82 | 2.44 | 2.52 |
| {{(filter odd? lr)}} | 2.95 | 2.70 | 2.08 | 2.07
所有基准测试都是在新生成的JVM上使用“bench”标准执行的,丢弃了具有大异常方差的结果。所用的通用基准模板形式为:{{(let [xs chunked-seq] (bench (doall (f xs))))}} 其中
* "chunked-seq" 是以下之一:{{(range 100000)}}, {{(range 1e5)}}, {{(vec (range 1e5))}} 或 {{(into (vector-of :int) (range 1e5))}}
* "doall" 是 {{doall}} 或 {{chunk-last}}(下面定义详见)
* "f" 是以下之一:{{(map inc xs)}}, {{(filter odd? xs)}} 或 {{(keep identity xs)}}.
观察
* 块越多、越大,优势越明显。更大的块(大于32的)的自定义(分块)序列可能会从这些更改中额外受益。
* 相同的更改使得 {{keep-indexed}} 和 {{map-indexed}} 更糟,所以它们没有进行更改。
* {{for}} 宏也具有块意识,但它使用显式循环来处理 {{:let, :when, :while}} 案例,这些案例难以与块缓冲区更改分离。
* {{chunk-last}} 是一个块意识函数,用于按块访问最后一个元素。与逐个遍历序列的 {{doall}} 相比,{{chunk-last}}对于更改前后的代码都更有效。该函数是:{{(defn chunk-last [xs] (when-let [xs (seq xs)] (if-let [cn (chunk-next xs)] (recur cn) (last xs))))}}
* 在 {{{map}}} 定义之前对 {{dotimes}} 的初始临时预定义可以从核心中删除。这包括在补丁中。