私は最近、予期せぬコードの最適化に直面し、私が観察していたことの解釈が正しいかどうかをチェックしたかったのです。以下の状況のはるかに簡単な例である:F#自動汎化とパフォーマンス
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つのブロック間の唯一の違いは、第二のケースでは、私は明示的に整数としてスワップの引数を宣言することです。しかし、#時間で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
が3倍速く最初よりも実行されます。ここでの絶対的なパフォーマンスの違いは明らかに問題ではありませんが、私がスワップ機能をたくさん使用すると、それは積み重なります。
私は、最初のケースではスワップが一般的であり、 "等価性が必要"であると仮定し、intがそれをサポートしているかどうかをチェックするのに対し、後者の場合は何もチェックする必要はありません。これがこれが起こっている理由なのでしょうか、それとも私は他の何かを逃していますか?そしてもっと一般的には、私は自動化された一般化を両刃の剣、すなわち性能に予期しない影響を及ぼすかもしれないすばらしい特徴と考えるべきですか?
おかげで、持っているとして、この問題は消えるだろうと思われます。したがって、私が正しく理解している場合、関数をインラインとしてマークすると、「この関数は汎用ですが、呼び出された場所で使用される型に基づいて特定のバージョンを作成しますか?数学スタイルのすべての関数をインラインとしてマークしない理由はありますか? – Mathias
あるいは、別の言い方をすれば、「あまりコードを生成しない」ということを少し詳しく説明できますが、これはあまり理解できません。 – Mathias
@Mathias 'inline'キーワードは、コンパイラが関数の呼び出しをその実装に置き換えることを意味します。これは、関数のコードがすべての呼び出しで1回繰り返されることを意味します。本当に長い関数の場合、アセンブリが大きくなります(またはJITに長時間かかる長いメソッドを生成する)。だからこそ私はこれを短い関数のためだけに使うことを勧めました。 –