この操作のパフォーマンスを大幅に向上させることができます。幸いなことに、このために Java を使用する必要はありません。正しく最適化すれば、Clojure は非常に高速であり、ほとんどの場合、純粋な Java と同じ速度を実現できます。
Clojure で数値コードの最大のパフォーマンスを得るには、以下を使用する必要があります。
- arrays、非常に高速な書き込みと検索を備えた可変ストレージが必要なため。Clojure のシーケンスとベクトルは美しいものですが、真にパフォーマンスが重要なコードではおそらく避けたいオーバーヘッドが伴います。
- doubleプリミティブは、はるかに高速な計算を提供するためです。
- aset / aget / areduce - これらは配列用に設計された非常に高速な操作であり、基本的に純粋な Java の同等物と同じバイトコードを提供します。
- 命令型スタイル- Clojure では一義的ですが、最速の結果が得られます (主に、メモリ割り当て、ボックス化、関数呼び出しによるオーバーヘッドを回避できるため)。例として、高速な命令型ループにdotimesを使用します。
- (set! *warn-on-reflection* true) - リフレクションは大きなパフォーマンス キラーであるため、コードが生成する警告を排除します。
以下は正しい線に沿っている必要があり、おそらく Java とほぼ同等のパフォーマンスが得られます。
(def kernel (double-array [0 1 1 2 3 3 0 0 0 0 0 0]))
(def data (double-array [1 5 7 4 8 3 9 5 6 3 2 1 1 7 4 9 3 2 1 8 6 4]))
(defn convolve [^doubles kernel-array ^doubles data-array]
(let [ks (count kernel-array)
ds (count data-array)
output (double-array (+ ks ds))
factor (/ 1.0 (areduce kernel-array i ret 0.0 (+ ret (aget kernel-array i))))]
(dotimes [i (int ds)]
(dotimes [j (int ks)]
(let [offset (int (+ i j))]
(aset output offset (+ (aget output offset) (* factor (* (aget data-array i) (aget kernel-array j))))))))
output))
(seq (convolve kernel data))
=> (0.0 0.1 0.6 1.4 2.4 4.4 5.5 6.1000000000000005 5.600000000000001 6.200000000000001 5.499999999999999 5.9 4.199999999999999 3.3000000000000003 2.5 2.2 3.3 4.4 5.6000000000000005 4.8 4.8999999999999995 3.1 3.5 4.300000000000001 5.0 3.0 1.2000000000000002 0.0 0.0 0.0 0.0 0.0 0.0 0.0)
出力配列をトリミングしたり、境界を設定したりしていないため、必要な出力を正確に取得するには、おそらくこのソリューションを少しハックする必要がありますが、うまくいけばアイデアが得られます.....
いくつかの非常に大まかなベンチマーク:
(time (dotimes [i 1000] (seq (convolve kernel data))))
=> "Elapsed time: 8.174109 msecs"
つまり、カーネル/データ ペアの組み合わせごとに約 30ns です。これは、キャッシュ メモリ アクセスの限界に達していると思います。