現在、F#プログラムのパフォーマンスを向上させて、C#相当の高速化を図っています。プログラムは、ピクセルのバッファにフィルタ配列を適用します。メモリへのアクセスは、常にポインタを使用して行われます。ここF#画像操作のパフォーマンスの問題
は、画像の各画素に適用されるC#コードである:
unsafe private static byte getPixelValue(byte* buffer, double* filter, int filterLength, double filterSum)
{
double sum = 0.0;
for (int i = 0; i < filterLength; ++i)
{
sum += (*buffer) * (*filter);
++buffer;
++filter;
}
sum = sum/filterSum;
if (sum > 255) return 255;
if (sum < 0) return 0;
return (byte) sum;
}
F#コードは次のように見え、限りC#プログラムの3倍を取る:
let getPixelValue (buffer:nativeptr<byte>) (filterData:nativeptr<float>) filterLength filterSum : byte =
let rec accumulatePixel (acc:float) (buffer:nativeptr<byte>) (filter:nativeptr<float>) i =
if i > 0 then
let newAcc = acc + (float (NativePtr.read buffer) * (NativePtr.read filter))
accumulatePixel newAcc (NativePtr.add buffer 1) (NativePtr.add filter 1) (i-1)
else
acc
let acc = (accumulatePixel 0.0 buffer filterData filterLength)/filterSum
match acc with
| _ when acc > 255.0 -> 255uy
| _ when acc < 0.0 -> 0uy
| _ -> byte acc
F#で可変変数とforループを使用すると、再帰を使用するのと同じ速度になります。すべてのプロジェクトは、コード最適化をオンにしてリリースモードで実行するように設定されています。
どのようにF#バージョンのパフォーマンスを改善できますか?
EDIT:
ボトルネックは(NativePtr.get buffer offset)
であると思われます。このコードを固定値で置き換え、C#バージョンの対応するコードを固定値で置き換えると、どちらのプログラムでもほぼ同じ速度になります。実際、C#では速度はまったく変わらないが、F#では大きな違いがある。
この動作は変更される可能性がありますか、F#のアーキテクチャに深く根ざしていますか?
EDIT 2:
Iは、forループを使用するために、再度コードをリファクタリング。実行速度は同じまま:
let mutable acc <- 0.0
let mutable f <- filterData
let mutable b <- tBuffer
for i in 1 .. filter.FilterLength do
acc <- acc + (float (NativePtr.read b)) * (NativePtr.read f)
f <- NativePtr.add f 1
b <- NativePtr.add b 1
Iは(NativePtr.read b)
を使用するバージョンとそれが固定値111uy
代わりのポインタから読み出しを使用することを除いて同じである別のバージョンのILコードを比較すると、のみILコード変更の次の行:
111uy
(NativePtr.read b)
はIL-コード行ldloc.s b
とldobj uint8
(1.4秒)を有するIL-コードldc.i4.s 0x6f
(0.3秒)を有し
比較のため:C#は0.4秒でフィルタリングを行います。
イメージバッファからの読み取り中にフィルタを読み取ってもパフォーマンスに影響を与えないという事実は、いくらか混乱します。私は画像の行をフィルタ処理する前に、私は行の長さを持つバッファに行をコピーします。そのため、読み取り操作はイメージ全体に広がっていませんが、このバッファ内にあり、サイズは約800バイトです。
最新のコメントに基づいて、私はあればC#コンパイラは、代わりに 'ldind.u1'を使用するという事実を疑問に思います'ldobj uint8'(F#が使用する意味的に等価なILです)では違いがあります。 'ldobj uint8'を' ldind.u1'に置き換えてilasmを実行して、パフォーマンスが異なるかどうかを調べることができます。 – kvb
私はそれを交換しましたが、違いはありませんでした。とにかくありがとう。 –