2012-02-07 9 views
6

.NETを使用して適切なアルゴリズムコーディングを行う方法を模索していました(たとえば、強力な型チェック、演算子のオーバーロード、ラムダ、汎用アルゴリズムが欲しいなど)。私は通常、C++でアルゴリズム(主に画像のプロジェクション)を書いています。 F#は言語としてはかなり面白いようですが、少し弾きましたが、非常に遅いようです。私はいくつかの配列操作をした最も簡単なテストとして - イメージの>明るさの増加: - より複雑なアルゴリズムのためにさらに悪化F#コードの最適化、またはそれは本当に遅いですか?

let r1 = rgbPixels |> Array.map (fun x -> x + byte(10)) 

少なくとも8倍の比較C++実装よりも遅くなるの要因であると思われます例えば2D畳み込み。 もっと速い方法があるのですか、特定のコンパイラ設定が欠けていますか(はい、最適化を使ってリリースをビルドしています...)? 私は素晴らしいと高い抽象的な支払いをしたいですが、そのようなオーバーヘッドはいいです(私は8コアを補うために並列化する必要があります:)) - 少なくともそれはさらに学ぶための動機を破壊する...私の他の私の重い藻類をC++で残し、C++とのインタフェースを取ることになりますが、管理されたラッパーのメンテナンスはかなり負担になります。

+1

インライン展開が可能ですが、 'byte'の呼び出しを' 10uy'に置き換えることで開始できます。 – Daniel

+0

C + +の実装を[ideone](http://ideone.com/)に投稿することができますか? – Daniel

+0

[これらの結果](http://ideone.com/Z4Gvj)はあなたのものに匹敵しますか? – Daniel

答えて

6

あなたが覚えておくべき重要なことの1つがパフォーマンスについて心配しているのであれば、F#はデフォルトで何も変更しないということです。これには、説明したようなアルゴリズムの多くの単純な実装でのコピーが必要です。

編集理由はわかりませんが、次のコードを単純なテストで実行すると、結果はArray.mapになります。これらの種類の最適化を実行するときに試してみるアルゴリズムをプロファイルしてください。しかし、私はformapの間で非常によく似た結果を得ました。

Array.mapは、操作の結果として新しい配列を作成します。代わりにArray.iteriが必要です。

rgbPixels |> Array.iteri (fun i x -> rgbPixels.[i] <- x + 10uy) 

関数型プログラミングの主要テナントの一つは限り不変オブジェクトに固執することですので、これはのように

module ArrayM = 
    let map f a = a |> Array.iteri (fun i x -> a.[i] <- f x) 

の下に独自のモジュールに包ますることができることを注意残念ながら、これは必要悪でありますあなたのアルゴリズムが許す限り、そして一度終わったら、性能が重要である突然変異に変更してください。もしあなたのパフォーマンスが遂に不可欠であることが分かっているなら、あなたはこれらの種類のヘルパーから始める必要があります。

また、この機能を提供するライブラリがある可能性がありますが、私はそれを知っているだけではありません。

+1

あるいは、 i = 0〜rgbPixels.Length-1 do rgbPixels [i] < - rgbPixels. [i] + 10uy'である。 – Daniel

+0

@Daniel:できるだけ元のコードに近づけようとしていました。とにかく、この関数はJITによってインライン展開されていると仮定します。 – Guvante

+0

@Guvante:FSIでテストしました。ループの時間が半分以下に短縮されました...おそらく境界チェックがないため、kvbが言及しました。 – Daniel

5

私はそれが慣用F#は、多くの場合、いくつかの理由のために、配列操作のために最適化されたC++のパフォーマンスを一致させるために失敗すると言っても安全だと思う:

  1. アレイのアクセスが配列の境界で照合されますメモリの安全性を保証する.NET。 CLRのJITコンパイラはいくつかのステレオタイプのコードの境界チェックを無効にすることができますが、通常はより慣用的なF#構造ではなく明示的な境界でforループを使用する必要があります。
  2. ラムダのような抽象化を使用するには、通常はわずかなオーバーヘッドがあります(例:fun i -> ...)。タイトなループでは、この小さなオーバーヘッドがループ本体で行われている作業と比べて重要になります。
  3. 私が知る限り、CLR JITコンパイラは、C++コンパイラと同じ程度にSSE命令を利用していません。総勘定元帳の他の側では

  1. あなたはF#コードでバッファオーバーフローを持つことはありません。
  2. あなたのコードは簡単に推論できます。結果として、与えられたレベルの総コード複雑度に対して、F#ではC++よりも複雑なアルゴリズムを実装することがよくあります。
  3. 必要に応じて、C++の速度に近い速度で実行されるユニコードコードを記述できます。また、パフォーマンス要件を満たすためにC++を記述する必要がある場合は、安全でないC++コードと相互運用するための機能もあります。
+0

1.このループはArray.map 2にあります。これはインライン展開で避けることができます3.一方で、並列化は(単一の追加の関数呼び出しを追加する場合と同じように)関数言語での方がはるかに簡単で、ベクトル演算を技術的に追加できます同様の方法で。 –

+0

私は上記のすべての点に完全に同意し、私は絶対的にいくつかのパフォーマンスを犠牲にしています(これはF#で遊ぶのを私のモチベーションでした)が、上記の測定結果は単純な操作のために5という係数を示しています私は何か悪いことをした)。並列化に着手する:5の要素から始めれば、5つのコアがビジー状態になり、C++で1つのコアを使用していた場所にいるかもしれません。 – TheBigW

+0

@Maciej - 配列に書き込む必要があるコード'Array'モジュールの上位関数のどれもforループと同じではありません(つまり' arr |> Array.iteri(fun iv - > arr。[i] < - v + 1) ')。読み込みは省略されますが、各繰り返しで書き込みを行うチェックがあります)。 – kvb

関連する問題