10

免責事項:これはマイクロベンチマークです。トピックに不満を感じる場合は、「時期尚早の最適化は悪です」などの引用にはコメントしないでください。

例は、x64、.Net4.5 Visual Studio 2012 F#3.0を対象としたリリースであり、Windows7x64で実行されます。

プロファイリング後、アプリケーションの1つのボトルネックを絞り込んだので、次の質問を提起したいと思います。

観察

for inループまたはの中にループがない場合Seq.iterは、どちらも同じ速度であることは明らかです。(update2とupdate4)

for inループまたはの中にループがある場合Seq.iter、それはのSeq.iter2倍速いようfor inです。(update vs update3)奇妙ですか?(fsiで実行した場合、それらは類似します)

anycpuを対象とし、x64で実行する場合、時間に違いはありません。したがって、質問は次のようになります。ターゲットがx64の場合、Seq.iter(update3)は2倍の速度を上げます

所要時間:

update:   00:00:11.4250483 // 2x as much as update3, why?
updatae2: 00:00:01.4447233
updatae3: 00:00:06.0863791
updatae4: 00:00:01.4939535

ソースコード:

open System.Diagnostics
open System

[<EntryPoint>]
let main argv = 
    let pool = seq {1 .. 1000000}

    let ret = Array.zeroCreate 100

    let update pool =
        for x in pool do
            for y in 1 .. 200 do
                ret.[2] <- x + y

    let update2 pool =
        for x in pool do
            //for y in 1 .. 100 do
                ret.[2] <- x


    let update3 pool =
        pool
            |> Seq.iter (fun x ->
                                  for y in 1 .. 200 do
                                      ret.[2] <- x + y)

    let update4 pool =
        pool
            |> Seq.iter (fun x ->
                                  //for y in 1 .. 100 do
                                      ret.[2] <- x)


    let test n =
        let run = match n with
                  | 1 -> update
                  | 2 -> update2
                  | 3 -> update3
                  | 4 -> update4
        for i in 1 .. 50 do
            run pool

    let sw = new Stopwatch()
    sw.Start()
    test(1)
    sw.Stop()
    Console.WriteLine(sw.Elapsed);

    sw.Restart()
    test(2)
    sw.Stop()
    Console.WriteLine(sw.Elapsed)

    sw.Restart()
    test(3)
    sw.Stop()
    Console.WriteLine(sw.Elapsed)

    sw.Restart()
    test(4)
    sw.Stop()
    Console.WriteLine(sw.Elapsed)
    0 // return an integer exit code
4

2 に答える 2

7

これは完全な答えではありませんが、さらに先に進むのに役立つことを願っています.

同じ構成を使用して動作を再現できます。プロファイリングの簡単な例を次に示します。

open System

let test1() =
    let ret = Array.zeroCreate 100
    let pool = {1 .. 1000000}    
    for x in pool do
        for _ in 1..50 do
            for y in 1..200 do
                ret.[2] <- x + y

let test2() =
    let ret = Array.zeroCreate 100
    let pool = {1 .. 1000000}    
    Seq.iter (fun x -> 
        for _ in 1..50 do
            for y in 1..200 do
                ret.[2] <- x + y) pool

let time f =
    let sw = new Diagnostics.Stopwatch()
    sw.Start()
    let result = f() 
    sw.Stop()
    Console.WriteLine(sw.Elapsed)
    result

[<EntryPoint>]
let main argv =
    time test1
    time test2
    0

この例では、Seq.iterfor x in poolは 1 回実行されますが、 と の間には 2 倍の時間差がtest1ありtest2ます。

00:00:06.9264843
00:00:03.6834886

これらの IL は非常に似ているため、コンパイラの最適化は問題になりません。x64 ジッタは で最適化できますが、最適化に失敗してtest1いるようtest2です。興味深いことに、ネストされた for ループをtest1関数としてリファクタリングすると、JIT 最適化が再び成功します。

let body (ret: _ []) x =
    for _ in 1..50 do
        for y in 1..200 do
            ret.[2] <- x + y

let test3() =
    let ret = Array.zeroCreate 100
    let pool = {1..1000000}    
    for x in pool do
        body ret x

// 00:00:03.7012302

ここで説明する手法を使用して JIT 最適化を無効にすると、これらの関数の実行時間は同等になります。

特定の例で x64 ジッターが失敗する理由はわかりません。最適化された Jitted コードを逆アセンブルして、ASM 命令を行ごとに比較できます。おそらく、ASM の知識が豊富な人なら、その違いを見つけることができるでしょう。

于 2012-11-07T16:04:23.193 に答える
1

自分のマシンで実験を実行すると (リリース モードの VS 2012 で F# 3.0 を使用)、記述された時間は得られません。繰り返し実行すると、一貫して同じ数値が得られますか?

私はそれを約4回試しましたが、常に非常に似た数字が得られます. のバージョンはSeq.iterわずかに高速になる傾向がありますが、これはおそらく統計的に有意ではありません。(を使用してStopwatch)のようなもの:

test(1) = 15321ms
test(2) = 5149ms
test(3) = 14290ms
test(4) = 4999ms

64 ビット Windows 7 を使用して、Intel Core2 Duo (2.26Ghz) を搭載したラップトップでテストを実行しています。

于 2012-11-04T12:48:36.803 に答える