我有一些模拟然后处理数字数据的Clojure代码。数据基本上是双值的向量;处理主要涉及以各种方式对它们的值进行求和。我将在下面添加一些代码,但我的问题是(我认为)更一般 - 我只是不知道如何解释hprof结果。
无论如何,我的测试代码是:
(defn spin [n]
(let [c 6000
signals (spin-signals c)]
(doseq [_ (range n)] (time (spin-voxels c signals)))))
(defn -main []
(spin 4))
其中spin-voxels
应该比spin-signals
贵(特别是重复多次时)。我可以给出较低级别的例程,但我认为这个问题更多的是关于我不理解痕迹的基础知识(下图)。
当我用lein编译它然后做一些简单的分析:
> java -cp classes:lib/clojure-1.3.0-beta1.jar -agentlib:hprof=cpu=samples,depth=10,file=hprof.vec com.isti.compset.stack
"Elapsed time: 14118.772924 msecs"
"Elapsed time: 10082.015672 msecs"
"Elapsed time: 9212.522973 msecs"
"Elapsed time: 12968.23877 msecs"
Dumping CPU usage by sampling running threads ... done.
,配置文件跟踪如下:
CPU SAMPLES BEGIN (total = 4300) Sun Aug 28 15:51:40 2011
rank self accum count trace method
1 5.33% 5.33% 229 300791 clojure.core$seq.invoke
2 5.21% 10.53% 224 300786 clojure.core$seq.invoke
3 5.05% 15.58% 217 300750 clojure.core$seq.invoke
4 4.93% 20.51% 212 300787 clojure.lang.Numbers.add
5 4.74% 25.26% 204 300799 clojure.core$seq.invoke
6 2.60% 27.86% 112 300783 clojure.lang.RT.more
7 2.51% 30.37% 108 300803 clojure.lang.Numbers.multiply
8 2.42% 32.79% 104 300788 clojure.lang.RT.first
9 2.37% 35.16% 102 300831 clojure.lang.RT.more
10 2.37% 37.53% 102 300840 clojure.lang.Numbers.add
非常酷。到此为止,我很高兴。我可以看到我浪费时间处理数值的通用处理。
所以我查看了我的代码并决定,作为第一步,我将vec
替换为d-vec
:
(defn d-vec [collection]
(apply conj (vector-of :double) collection))
我不确定这是否足够 - 我怀疑我还需要在不同的地方添加一些类型的注释 - 但这似乎是一个好的开始。所以我再次编译和分析:
> java -cp classes:lib/clojure-1.3.0-beta1.jar -agentlib:hprof=cpu=samples,depth=10,file=hprof.d-vec com.isti.compset.stack
"Elapsed time: 15944.278043 msecs"
"Elapsed time: 15608.099677 msecs"
"Elapsed time: 16561.659408 msecs"
"Elapsed time: 15416.414548 msecs"
Dumping CPU usage by sampling running threads ... done.
EWWW。所以它明显变慢了。和个人资料?
CPU SAMPLES BEGIN (total = 6425) Sun Aug 28 15:55:12 2011
rank self accum count trace method
1 26.16% 26.16% 1681 300615 clojure.core.Vec.count
2 23.28% 49.45% 1496 300607 clojure.core.Vec.count
3 7.74% 57.18% 497 300608 clojure.lang.RT.seqFrom
4 5.59% 62.77% 359 300662 clojure.core.Vec.count
5 3.72% 66.49% 239 300604 clojure.lang.RT.first
6 3.25% 69.74% 209 300639 clojure.core.Vec.count
7 1.91% 71.66% 123 300635 clojure.core.Vec.count
8 1.03% 72.68% 66 300663 clojure.core.Vec.count
9 1.00% 73.68% 64 300644 clojure.lang.RT.more
10 0.79% 74.47% 51 300666 clojure.lang.RT.first
11 0.75% 75.22% 48 300352 clojure.lang.Numbers.double_array
12 0.75% 75.97% 48 300638 clojure.lang.RT.more
13 0.64% 76.61% 41 300621 clojure.core.Vec.count
14 0.62% 77.23% 40 300631 clojure.core.Vec.cons
15 0.61% 77.84% 39 300025 java.lang.ClassLoader.defineClass1
16 0.59% 78.43% 38 300670 clojure.core.Vec.cons
17 0.58% 79.00% 37 300681 clojure.core.Vec.cons
18 0.54% 79.55% 35 300633 clojure.lang.Numbers.multiply
19 0.48% 80.03% 31 300671 clojure.lang.RT.seqFrom
20 0.47% 80.50% 30 300609 clojure.lang.Numbers.add
我在这里添加了更多行,因为这是我不理解的部分。
为什么 earth Vec.count
经常出现?这是一个返回向量大小的方法。单行查找属性。
我认为我比较慢,因为我仍然在Double和double之间来回跳跃,当我添加更多类型注释时,事情可能会再次改善。但是我不明白我现在拥有什么,所以我不太确定愚蠢的fowards有多大意义。
请问,任何人都可以一般性地解释上面的转储吗?我保证不会多次调用count
- 而是我有很多地图并减少了一些显式循环。
我想知道我是否可能被JIT弄糊涂了?也许我错过了一堆信息,因为函数被内联了?哦,我正在使用1.3.0-beta1,因为它似乎有更明智的数字处理。
[更新]我在http://www.acooke.org/cute/Optimising1.html总结了我的经验 - 加速了5倍(实际上是在清理了一些并且移动到1.3之后的10倍),尽管从来没有理解这一点。
答案 0 :(得分:1)
为Vec对象(由vector-of创建的对象)调用seq会创建一个VecSeq对象。
在Veca上创建的VecSeq对象调用Vec.count的方法是internal-reduce,它由clojure.core / reduce使用。
因此,似乎是一个向量调用Vec.count创建的向量,同时减少了。正如你所提到的,代码做了很多减少,这似乎是原因
Vec.count的另一个原因是Vec.count似乎非常简单:
clojure.lang.Counted
(count [_] cnt)
一个简单的吸气剂,不做任何计数。
答案 1 :(得分:0)
只是大声说话,看起来你的代码在Seq上进行了大量的来回转换。
查看RT.seqFrom,这会调用ArraySeq.createFromObject
if(array == null || Array.getLength(array) == 0)
return null;
那就是使用vec使用快速矢量访问,并且使用d-vec强制使用数组并调用缓慢的java.lang.Array.getLength方法(使用反射..)