4

私はこれを思いつきました:

(def kernel [0 1 1 2 3 3 0 0 0 0 0 0])
(def data [1 5 7 4 8 3 9 5 6 3 2 1 1 7 4 9 3 2 1 8 6 4])

(defn capped+ [a b c] (let [s (+ a b)] (if (> s c) c s)))

(defn *+ [a b]
  (if (> (count a) (count b))  
    (reduce + (map-indexed (fn _ [i x] (* (a i) (b i))) b))
    (reduce + (map-indexed (fn _ [i x] (* (a i) (b i))) a))))

(defn slide*i [d k] 
 (let [ki (into [] (reverse k)) kl (count k) dl (count d)]
   (map-indexed 
      (fn [idx item] 
        (/ (*+ ki (subvec d idx (capped+ idx kl dl))) 
           (reduce + ki))) 
      d)))

(def s (slide*i data kernel))

これは最も洗練されたコードではありませんが、問題なく動作します。私は実際にいくつかの非常にとがった部分を滑らかにするためにそれを使用したい! データ。

これをより美しく、より効率的に、またはより正確にするための提案は歓迎されます (私の場合は決して使用しないため、個人的にはテールが不正確であることは気にしません)。

4

3 に答える 3

8

この操作のパフォーマンスを大幅に向上させることができます。幸いなことに、このために 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 です。これは、キャッシュ メモリ アクセスの限界に達していると思います。

于 2011-11-24T11:46:47.150 に答える
4
; 未使用ですが、デモ用に残しました
(defn capped+ [abc]
  (最小 (+ ab) c))

(定義 *+ [ab]
  (reduce + (map * ab)))

(defn slide*i [dk]
  (let [ki (逆 k)
        kl (k をカウント)
        ks (リデュース + k)]
    (マップ #(/ (*+ %1 ki) ks) (パーティション kl 1 d))))

を使用partitionすると、結果は次のようになります。

(59/10 21/5 33/10 5/2 11/5 33/10 22/5 28/5 24/5 49/10 31/10)

しかし、 を使用するとpartition-all、ソリューションの結果が正確に得られます。

(59/10 21/5 33/10 5/2 11/5 33/10 22/5 28/5 24/5 49/10 31/10 7/2 43/10 5 3 6/5 0 0 0 0 0 0)
于 2011-11-24T01:24:01.797 に答える
1

これを行う効率的な方法は、畳み込みを行う Java クラスを作成し、それを clojure から呼び出し、可能であれば Java 配列を渡すことです。Clojure の実装は、効率が問題になる場合は、Java 配列でも動作する必要があります。

于 2011-11-24T01:20:41.707 に答える