2012-05-04 11 views
7

コードの例:F#の最適化

let foo1 (arr : int[]) = 
    for i = 0 to arr.Length-1 do 
     arr.[i] <- i 

let foo2 (arr : int[]) = 
    for i in [0..arr.Length-1] do 
     arr.[i] <- i 

私はこの関数は(性能の点で)互いに等価であるべきと考えました。我々はILリストに見ればしかし、我々が表示されます:

まず機能、15行を、動的な割り当て、無tryオペレータ、ノー仮想呼び出し:

IL_0000: nop 
IL_0001: ldc.i4.0 
IL_0002: stloc.0 
IL_0003: br.s IL_0011 
// loop start (head: IL_0011) 
    IL_0005: ldarg.0 
    IL_0006: ldloc.0 
    IL_0007: ldloc.0 
    IL_0008: stelem.any [mscorlib]System.Int32 
    IL_000d: ldloc.0 
    IL_000e: ldc.i4.1 
    IL_000f: add 
    IL_0010: stloc.0 

    IL_0011: ldloc.0 
    IL_0012: ldarg.0 
    IL_0013: ldlen 
    IL_0014: conv.i4 
    IL_0015: blt.s IL_0005 
// end loop 

IL_0017: ret 

と第二1 - ほぼ100ライン、割り当て/割り当て解除をたくさん、仮想関数の召し、try/Disposeがたくさん:F#コンパイラがfoo2ためそれほど複雑なコードを使用しない理由

IL_0000: nop 
IL_0001: ldc.i4.0 
IL_0002: ldc.i4.1 
IL_0003: ldarg.0 
IL_0004: ldlen 
IL_0005: conv.i4 
IL_0006: ldc.i4.1 
IL_0007: sub 
IL_0008: call class [mscorlib]System.Collections.Generic.IEnumerable`1<int32> [FSharp.Core]Microsoft.FSharp.Core.Operators/OperatorIntrinsics::RangeInt32(int32, int32, int32) 
IL_000d: call class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0> [FSharp.Core]Microsoft.FSharp.Core.Operators::CreateSequence<int32>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>) 
IL_0012: call class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1<!!0> [FSharp.Core]Microsoft.FSharp.Collections.SeqModule::ToList<int32>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>) 
IL_0017: stloc.0 
IL_0018: ldloc.0 
IL_0019: unbox.any class [mscorlib]System.Collections.Generic.IEnumerable`1<int32> 
IL_001e: callvirt instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> class [mscorlib]System.Collections.Generic.IEnumerable`1<int32>::GetEnumerator() 
IL_0023: stloc.1 
.try 
{ 
    // loop start (head: IL_0024) 
     IL_0024: ldloc.1 
     IL_0025: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext() 
     IL_002a: brfalse.s IL_003e 

     IL_002c: ldloc.1 
     IL_002d: callvirt instance !0 class [mscorlib]System.Collections.Generic.IEnumerator`1<int32>::get_Current() 
     IL_0032: stloc.3 
     IL_0033: ldarg.0 
     IL_0034: ldloc.3 
     IL_0035: ldloc.3 
     IL_0036: stelem.any [mscorlib]System.Int32 
     IL_003b: nop 
     IL_003c: br.s IL_0024 
    // end loop 

    IL_003e: ldnull 
    IL_003f: stloc.2 
    IL_0040: leave.s IL_005b 
} // end .try 
finally 
{ 
    IL_0042: ldloc.1 
    IL_0043: isinst [mscorlib]System.IDisposable 
    IL_0048: stloc.s 4 
    IL_004a: ldloc.s 4 
    IL_004c: brfalse.s IL_0058 

    IL_004e: ldloc.s 4 
    IL_0050: callvirt instance void [mscorlib]System.IDisposable::Dispose() 
    IL_0055: ldnull 
    IL_0056: pop 
    IL_0057: endfinally 

    IL_0058: ldnull 
    IL_0059: pop 
    IL_005a: endfinally 
} // end handler 

IL_005b: ldloc.2 
IL_005c: pop 
IL_005d: ret 

私の質問はありますか?なぜ簡単なループを実装するのにIEnumerableを使用するのですか?あなたは範囲式を使用している場合

答えて

12

は、第二の例では、それは通常のforループに変換されます。

let foo2 (arr : int[]) = 
    for i in 0..arr.Length-1 do 
     arr.[i] <- i 

foo1と等価になります。私はSection 6.3.12 Range Expressions in F# language specsを引用

シーケンスVARのフォームの発現反復のexpr1とexpr2の.. DO expr3はで行われ、時にはループ表現 (§6.5.7)のためのシンプルとして精緻化されます。あなたが明示的に新しいリストを作成している

let foo2 (arr : int[]) = 
    let xs = [0..arr.Length-1] (* A new list is created *) 
    for i in xs do 
     arr.[i] <- i 

はしかし、あなたの第二の例では、より多くのようなものです。

+0

私は0..arr.Length-1 do'式の 'for iについては知りませんでした。どうもありがとう。 – qehgt

7

インデックスベースの列挙型とIEnumerableベースの列挙型(またはC#用語forforeach)の標準的な違いは何ですか。

2番目のサンプルでは、​​式[0..arr.Length-1]がコレクションを作成していて、F#は値を列挙するためにIEnumerable<T>を使用しています。この列挙型の一部はIEnumerator<T>の使用を伴い、IDisposableを実装します。表示されているtry/finallyブロックが生成され、例外が発生してもIDisposable::Disposeメソッドが列挙の最後に呼び出されるようにします。

F#は2番目の例を最初のものに最適化し、余分なオーバーヘッドをすべて避けることができますか?彼らはこの最適化を行うことができる可能性があります。基本的に範囲式を覗いてみましょう。単なる数値範囲ではないので、同等のforコードを生成します。

F#は2番目の例を最適化する必要があります。私の投票はノーとなるでしょう。このような機能は、しばしば外部からは見えませんが、実際にそれらを実装し、より重要なことに、それらを維持することはかなり高価になる可能性があります。巧みなユーザーは、標準のforバージョンにコードを変換して、IEnumerable<T>のオーバーヘッドを避けることができます(プロファイラが問題になることがわかった場合)。最適化を実装しないとF#チームは他の素晴らしい機能を実装することができます。