13

Reading Beginning F# - Robert Pickering私は次の段落に注目しました。

OCaml のバックグラウンドを持つプログラマーは、F# で例外を使用するときに注意する必要があります。CLR のアーキテクチャのため、例外のスローは非常にコストがかかります。OCaml よりもかなりコストがかかります。多くの例外をスローする場合は、コードを慎重にプロファイリングして、パフォーマンス コストに見合う価値があるかどうかを判断してください。コストが高すぎる場合は、コードを適切に修正してください。

なぜ、CLR のために、例外をスローする方が よりもコストがかかるF#OCamlですか? この場合、コードを適切に修正する最善の方法は何ですか?

4

3 に答える 3

12

CLR の例外は非常に豊富で、多くの詳細を提供します。Rico Marianiは、CLRの例外のコストに関する (古いが関連性のある) 投稿を投稿し、これについて詳しく説明しています。

そのため、CLR で例外をスローすると、OCaml などの他の環境よりも相対的なコストが高くなります。

この場合、コードを適切に修正する最善の方法は何ですか?

通常の非例外的な状況で例外が発生することが予想される場合は、例外を完全に回避する方法でアルゴリズムと API を再考することができます。たとえば、例外が発生する前に状況をテストできる代替 API を提供するようにしてください。

于 2012-06-09T21:56:24.860 に答える
8

なぜ、CLR のせいで、F# の方が OCaml よりも例外のスローにコストがかかるのですか?

OCaml は、制御フローとして例外を使用するために大幅に最適化されました。対照的に、.NET の例外はまったく最適化されていません。

パフォーマンスの違いは非常に大きいことに注意してください。OCaml の例外は、F# よりも約 600 倍高速です。私のベンチマークによると、C++ でさえ、この点で OCaml より約 6 倍遅いです。

.NET の例外はもっと多くのことを提供すると言われていますが (OCaml はスタック トレースを提供しますが、これ以上何が必要ですか?)、それらがそれほど遅くなるべき理由は思いつきません。

この場合、コードを適切に修正する最善の方法は何ですか?

F# では、「合計」関数を記述する必要があります。これは、関数が結果の種類 (通常または例外など) を示す共用体型の値を返す必要があることを意味します。

特に、 への呼び出しは、 valueであるか、キー要素がコレクションに存在しなかった場合に型の値を返す へのfind呼び出しに置き換える必要があります。tryFindoptionSomeNone

于 2012-06-10T18:35:44.330 に答える
7

Reed は、.NET の例外が OCaml の例外と異なる動作をする理由について既に説明しました。一般に、.NET の例外は例外的な状況にのみ適しており、その目的のために設計されています。OCaml はより軽量なモデルを持っているため、一部の制御フロー パターンの実装にも使用されます。

具体的な例を挙げると、OCaml では例外を使用してループの中断を実装できます。たとえばtest、数値が必要な数値であるかどうかをテストする関数があるとします。以下は、1 から 100 までの数値を繰り返し処理し、最初に一致した数値を返します。

// Simple exception used to return the result
exception Returned of int

try
  // Iterate over numbers and throw if we find matching number
  for n in 0 .. 100 do
    printfn "Testing: %d" n
    if test n then raise (Returned n)
  -1                 // Return -1 if not found
with Returned r -> r // Return the result here

これを例外なく実装するには、2 つのオプションがあります。同じ動作を持つ再帰関数を作成できます-呼び出す場合(そして、C# でループ内をfind 0使用するのと本質的に同じ IL コードにコンパイルされます):return nfor

let rec find n = 
  printfn "Testing: %d" n
  if n > 100 then -1  // Return -1 if not found
  elif test n then n  // Return the first found result 
  else find (n + 1)   // Continue iterating

再帰関数を使用したエンコードは少し長くなる可能性がありますが、F# ライブラリが提供する標準関数を使用することもできます。これは、多くの場合、制御フローに OCaml 例外を使用するコードを書き直すための最良の方法です。この場合、次のように記述できます。

// Find the first value matching the 'test' predicate
let res = seq { 0 .. 100 } |> Seq.tryFind test
// This returns an option type which is 'None' if the value 
// was not found and 'Some(x)' if the value was found.
// You can use pattern matching to return '-1' in the default case:
match res with
| None -> -1
| Some n -> n

オプションの種類に慣れていない場合は、入門資料を参照してください。F# wikibook には優れたチュートリアルがあり、MSDN ドキュメントにも役立つ例があります。

モジュールから適切な関数を使用するとSeq、多くの場合、コードが大幅に短くなるため、望ましいです。再帰を直接使用するよりもわずかに効率が悪いかもしれませんが、ほとんどの場合、それについて心配する必要はありません。

編集:実際のパフォーマンスに興味がありました。入力がリストではなく遅延生成されたシーケンスである場合、使用するバージョンSeq.tryFindはより効率的です(リスト割り当てのコストのため)。これらの変更と25 番目の要素を返す関数を使用すると、私のマシンでコードを 100000 回実行するのに必要な時間は次のようになります。seq { 1 .. 100 }[ 1 .. 100 ]test

exceptions   2.400sec  
recursion    0.013sec  
Seq.tryFind  0.240sec

Seqこれは非常に些細なサンプルなので、再帰を使用して記述された同等のコードよりも、一般に 10 倍遅く実行されることはないと思います。速度低下はおそらく、追加のデータ構造 (シーケンスを表すオブジェクト、クロージャーなど) の割り当てと、追加の間接化 (単純な数値演算とジャンプではなく、多数の仮想メソッド呼び出しが必要になるコード) が原因であると考えられます。ただし、例外はさらにコストがかかり、コードを短くしたり、読みやすくしたりしません...

于 2012-06-09T22:25:55.150 に答える