4

私の考えでは、Clojure ベクトルは Java 配列に比べてわずかにパフォーマンスが低下します。その結果、「従来の知恵」は、コードのパフォーマンスが重要な部分については、Java 配列を使用する方がよいと考えていました。

ただし、私のテストでは、これは正しくないことが示唆されています。

Clojure 1.3.0
user=> (def x (vec (range 100000)))
#'user/x
user=> (def xa (int-array x))
#'user/xa
user=> (time  (loop [i 0 s 0] (if (< i 100000) (recur (inc i) (+ s (nth x i))) s)))
"Elapsed time: 16.551 msecs"
4999950000
user=> (time  (loop [i 0 s 0] (if (< i 100000) (recur (inc i) (+ s (aget xa i))) s)))
"Elapsed time: 1271.804 msecs"
4999950000

ご覧のとおり、get はこの追加に約 800% の時間を追加します。ただし、どちらの方法もネイティブ Java よりもはるかに低速です。

public class Test {                                                                                                                                                                                                                                                                                                           
    public static void main (String[] args) {                                                                                                                                                                                                                                                                                 
        int[] x = new int[100000];                                                                                                                                                                                                                                                                                            
        for (int i=0;i<100000;i++) {                                                                                                                                                                                                                                                                                          
            x[i]=i;                                                                                                                                                                                                                                                                                                           
        }                                                                                                                                                                                                                                                                                                                     
        long s=0;                                                                                                                                                                                                                                                                                                             
        long end, start = System.nanoTime();                                                                                                                                                                                                                                                                                  
        for (int i=0;i<100000;i++) {                                                                                                                                                                                                                                                                                          
            s+= x[i];                                                                                                                                                                                                                                                                                                         
        }                                                                                                                                                                                                                                                                                                                     
        end = System.nanoTime();                                                                                                                                                                                                                                                                                              
        System.out.println((end-start)/1000000.0+" ms");                                                                                                                                                                                                                                                                      
        System.out.println(s);                                                                                                                                                                                                                                                                                                
    }                                                                                                                                                                                                                                                                                                                         
}                              

> java Test
1.884 ms
4999950000

では、get は nth よりも 80 倍遅く、Java の []-access よりも約 800 倍遅いという私の結論を下すべきでしょうか?

4

3 に答える 3

9

これは、get 関数によるプリミティブ型のリフレクションとオートボクシングが原因であると思われます....

幸いなことに、aget/aset には、リフレクションを回避し、直接 array[i] アクセスを行うだけの、プリミティブ配列に対するパフォーマンスの高いオーバーロードがあります (こちらこちらを参照してください)。

適切な関数を選択するには、タイプ ヒントを渡すだけです。

(type xa)
[I    ; indicates array of primitive ints

; with type hint on array
;
(time (loop [i 0 s 0] 
        (if (< i 100000) (recur (inc i) 
          (+ s (aget ^ints xa i))) s))) 
"Elapsed time: 6.79 msecs"
4999950000

; without type hinting
;
(time (loop [i 0 s 0] 
        (if (< i 100000) (recur (inc i) 
          (+ s (aget xa i))) s)))
"Elapsed time: 1135.097 msecs"
4999950000
于 2012-04-12T23:35:33.077 に答える
4

型ヒントはまったく必要ないように思われますが、Clojure はそのままでうまく最適化されます。

コレクションに対してポリアディック関数を実行する必要がある場合は、apply と関数を使用してください。コレクション内の要素に関数を適用し、結果をアキュムレータに格納する必要がある場合は、reduce を使用します。この場合、両方に当てはまります。

=> (def xa (into-array (range 100000)))
#'user/xa

=> (time (apply + xa))
"Elapsed time: 12.264753 msecs"
4999950000

=>(time (reduce + xa))
"Elapsed time: 2.735339 msecs"
4999950000

上記の最良のケースよりもわずかに遅くなりますが、さらに単純にこれらの違いを均等にします。

=> (def xa (range 100000))
#'user/xa

=> (time (apply + xa))
"Elapsed time: 4.547634 msecs"
4999950000

=> (time (reduce + xa))
"Elapsed time: 4.506572 msecs"

可能な限り単純なコードを書いてみて、それが十分に速くない場合にのみ最適化してください。

于 2012-04-13T09:39:06.620 に答える
4

リフレクションがすべてのテストの精度を洗い流しているようです:

user> (set! *warn-on-reflection* true)
true
user> (def x (vec (range 100000)))
#'user/x
user>  (def xa (int-array x))
#'user/xa
user>  (time  (loop [i 0 s 0] (if (< i 100000) (recur (inc i) (+ s (nth x i))) s)))
NO_SOURCE_FILE:1 recur arg for primitive local: s is not matching primitive, had: Object, needed: long
Auto-boxing loop arg: s
"Elapsed time: 12.11893 msecs"
4999950000
user> (time  (loop [i 0 s 0] (if (< i 100000) (recur (inc i) (+ s (aget xa i))) s)))
Reflection warning, NO_SOURCE_FILE:1 - call to aget can't be resolved.
NO_SOURCE_FILE:1 recur arg for primitive local: s is not matching primitive, had: Object, needed: long
Auto-boxing loop arg: s
Reflection warning, NO_SOURCE_FILE:1 - call to aget can't be resolved.
"Elapsed time: 2689.865468 msecs"
4999950000
user> 

2 番目のものはたまたま反射が多いだけです。

この種のベンチマークを実行するときは、必ず何度も実行して hotSpot コンパイラをウォームアップしてください。

user> (time  (loop [i 0 s 0] (if (< i 100000) (recur (inc i) (+ s (aget xa i))) (long s))))
"Elapsed time: 3135.651399 msecs"
4999950000
user> (time  (loop [i 0 s 0] (if (< i 100000) (recur (inc i) (+ (long s) (aget xa i))) (long s))))
"Elapsed time: 1014.218461 msecs"
4999950000
user> (time  (loop [i 0 s 0] (if (< i 100000) (recur (inc i) (+ (long s) (aget xa i))) (long s))))
"Elapsed time: 998.280869 msecs"
4999950000
user> (time  (loop [i 0 s 0] (if (< i 100000) (recur (inc i) (+ (long s) (aget xa i))) (long s))))
"Elapsed time: 970.17736 msecs"
4999950000

この場合、数回の実行で元の時間の 1/3 にまで短縮されました (ただし、ここでもリフレクションが主な問題です)。

両方を dotimes でウォームアップすると、結果が大幅に改善されます。

(dotimes [_ 1000]  (time  (loop [i 0 s 0] (if (< i 100000) (recur (inc i) (+ s (nth x  i))) s))))
"Elapsed time: 3.704714 msecs"

(dotimes [_ 1000] (time  (loop [i 0 s 0] (if (< i 100000) (recur (inc i) (+ (long s) (aget xa i))) (long s)))))
"Elapsed time: 936.03987 msecs"
于 2012-04-12T23:35:02.720 に答える