7

最近、予期しないコードの最適化に遭遇したので、観察した内容の解釈が正しいかどうかを確認したいと思いました。以下は、状況の非常に単純化された例です。

let demo =
   let swap fst snd i =
       if i = fst then snd else
       if i = snd then fst else
       i
   [ for i in 1 .. 10000 -> swap 1 i i ]

let demo2 =
   let swap (fst: int) snd i =
       if i = fst then snd else
       if i = snd then fst else
       i
   [ for i in 1 .. 10000 -> swap 1 i i ] 

2 つのコード ブロックの唯一の違いは、2 番目のケースでは swap の引数を明示的に整数として宣言していることです。それでも、#time を指定して fsi で 2 つのスニペットを実行すると、次のようになります。

ケース 1 実数: 00:00:00.011、CPU: 00:00:00.000、GC gen0: 0、gen1: 0、gen2: 0
ケース 2 実数: 00:00:00.004、CPU: 00:00:00.015、GC gen0 : 0、gen1: 0、gen2: 0

つまり、2 番目のスニペットは最初のスニペットよりも 3 倍速く実行されます。ここでの絶対的なパフォーマンスの違いは明らかに問題ではありませんが、swap 関数を頻繁に使用すると、それが積み重なってしまいます。

私の推測では、パフォーマンス ヒットの理由は、最初のケースでは swap が汎用的で「等価性が必要」であり、int がそれをサポートしているかどうかをチェックするのに対し、2 番目のケースでは何もチェックする必要がないためです。これがこれが起こっている理由ですか、それとも何か他のものを見逃していますか? もっと一般的に言えば、自動汎化は諸刃の剣、つまり、パフォーマンスに予期しない影響を与える可能性のある優れた機能であると考えるべきでしょうか?

4

2 に答える 2

11

これは、 Why is this F# code so slowという質問とほぼ同じケースだと思います。その質問では、パフォーマンスの問題は必要な制約によって引き起こされ、comparisonあなたの場合、それはequality制約によって引き起こされます。

どちらの場合も、コンパイルされた汎用コードはインターフェイス (およびボックス化) を使用する必要がありますが、特殊なコンパイル済みコードは、整数または浮動小数点数の比較または等価のために IL 命令を直接使用できます。

パフォーマンスの問題を回避するには、次の 2 つの方法があります。

  • 使用するコードを特殊化するintfloat、使用したように
  • inlineコンパイラが自動的に特殊化するように関数をマークします

小さい関数の場合は、2 番目のアプローチの方が優れています。これは、コードがあまり生成されず、汎用的な方法で関数を記述できるためです。(設計上) 単一の型に対してのみ関数を使用する場合は、最初のアプローチを使用するのがおそらく適切です。

于 2012-05-06T23:24:01.327 に答える
4

違いの理由は、コンパイラが呼び出しを生成しているためです。

    IL_0002:  call bool class [FSharp.Core]Microsoft.FSharp.Core.LanguagePrimitives/HashCompare::GenericEqualityIntrinsic<!!0> (!!0, !!0)

ジェネリック バージョンでは、intバージョンは直接比較できます。

使用した場合inline、コンパイラに追加の型情報が含まれるようになったため、この問題は解消されると思います

于 2012-05-06T23:25:01.550 に答える