2017-02-06 15 views
8

非常に単純なC#とF#テストプログラムを作成しました。 #71、MS のF#1797ミリなぜLINQ(c#)とSeq(f#)のパフォーマンスの違いがありますか

C

using System; 
using System.Collections.Generic; 
using System.Diagnostics; 
using System.Linq; 

namespace ConsoleApplication2 
{ 
    class Program 
    { 
     static int Remainder(int num) 
     { 
      return num % 2; 
     } 
     static int SumOfremainders(IEnumerable<int> list) 
     { 
      var sum = 0; 
      foreach (var num in list) 
      { 
       sum += Remainder(num); 
      } 
      return sum; 
     } 

     static void Main(string[] args) 
     { 
      Stopwatch sw = new Stopwatch(); 
      var nums = Enumerable.Range(1, 10000000); 
      sw.Start(); 
      var a = SumOfremainders(nums); 
      sw.Stop(); 
      Console.WriteLine("Duration " + (sw.ElapsedMilliseconds)); 
      Console.WriteLine("Sum of remainders: {0}", a); 
     } 
    } 
} 


let remainder x = x % 2 

let sumORemainders n = 
    n 
    |> Seq.map(fun n-> remainder n) 
    |> Seq.sum 

let seqb = Seq.init 10000000(fun n->n) 
let timer =System.Diagnostics.Stopwatch() 
timer.Start() 
let a =(sumORemainders seqb) 
timer.Stop() 
printfn "Elapsed Time: " 
System.Console.WriteLine timer.ElapsedMilliseconds 
printfn "Sum of squares of 1-100: %d" a 


[<EntryPoint>] 
let main argv = 

    0 // return an integer exit code 

を私は#

let remainder x = x % 2 

let sumORemainders (input:seq<int>) = 
    let mutable sum = 0 
    let en = input.GetEnumerator() 
    while (en.MoveNext()) do 
     sum <- sum + remainder en.Current 
    sum 


let seqb = Seq.init 10000000(fun n->n) 
let timer =System.Diagnostics.Stopwatch() 
timer.Start() 
let a =(sumORemainders seqb) 
timer.Stop() 
printfn "Elapsed Time: " 
System.Console.WriteLine timer.ElapsedMilliseconds 
printfn "Sum of squares of 1-100: %d" a 


[<EntryPoint>] 
let main argv = 

    0 // return an integer exit code 

cより同様の動作F位から第二のバージョンを作ったが、結果は、有意に(1650ms)に変更しないではありません私は2つの言語間の速度の大きな違いを理解していません。

2つのプログラムは非常に似たILコードを持ち、どちらもIEnumerableを使用します。さらに、F#は関数呼び出しを操作に置き換えます。

私はf#ILコードに基づいてc#コードを書き換えました。

static int SumOfremainders(IEnumerable<int> list) 
     { 
      var sum = 0; 
      IEnumerator<int> e = list.GetEnumerator(); 
      while (e.MoveNext()) 
      { 
       sum += e.Current % 2; 
      } 
      return sum; 
     } 

2つのプログラムのILコードは同じですが、速度はまだ非常に異なります。 私はフォギーファインダー

遅いコード

[CompilationMapping(SourceConstructFlags.Module)] 
public static class Program 
{ 
    [Serializable] 
    internal class [email protected] : FSharpFunc<int, int> 
    { 
     internal [email protected]() 
     { 
     } 

     public override int Invoke(int n) 
     { 
      return n; 
     } 
    } 

    [CompilationMapping(SourceConstructFlags.Value)] 
    public static IEnumerable<int> seqb 
    { 
     get 
     { 
      return [email protected]; 
     } 
    } 

速いコード

[CompilationMapping(SourceConstructFlags.Module)] 
public static class Program 
{ 
    [CompilationMapping(SourceConstructFlags.Value)] 
     public static int[] seqb 
     { 
      get 
      { 
       return [email protected]; 
      } 
     } 
+2

私は質問が重複していることに同意しません。もう1つの質問は開いていますが、これははるかに具体的です。しかし、私はOPが 'Seq/LINQ'を含むように質問を言い換えるべきだと思う(LINQは遅いSeqは遅い) – FuleSnabel

+0

これはあなたのC#コードとF#が実際に異なっているからです。 –

+0

これは: 'let seqb = Seq.init 10000000(fun n-> n)'は 'var nums = Enumerable.Range(1、10000000);と等しくないので、 –

答えて

8

OPはF#でSeq.initが遅いので、性能差がある見ている主な理由のためのIL差感謝を発見しました。その理由は、各繰り返しにおいて、Seq.uptoSeq.initが使用する)が新しいLazy<_>オブジェクトを割り当てるからです。これはSeqsourceにあります。 fun n -> n % 2のような低オーバーヘッド機能の場合、新しいLazy<_>オブジェクトのコストと評価(ミューテックスのロックとロック解除)にはかなりの時間がかかります。

OPがパフォーマンスの違いを感じる第2の理由は、F#のSeqが一般的に遅いということです。このPRのインプレースのF#SeqでこのPR

にmanofstickによって改善されている(いくつかの詳細here)が存在する代替案に比べて非常によく実行されます

このすべては、私はさまざまな方法のいくつかの比較を準備言ってユーザーが投稿した計算を実行します(明白なtotal/2は別です)。

open CsPerfs 
open Nessos.Streams 
open System.Diagnostics 
open System.Linq 
open System.Numerics 


// now() returns current time in milliseconds since start 
let now : unit -> int64 = 
    let sw = Stopwatch() 
    sw.Start() 
    fun() -> sw.ElapsedMilliseconds 

// time estimates the time 'action' repeated a number of times 
let time repeat action = 
    let inline cc i  = System.GC.CollectionCount i 

    let v     = action() 

    System.GC.Collect (2, System.GCCollectionMode.Forced, true) 

    let bcc0, bcc1, bcc2 = cc 0, cc 1, cc 2 
    let b     = now() 

    for i in 1..repeat do 
    action() |> ignore 

    let e = now() 
    let ecc0, ecc1, ecc2 = cc 0, cc 1, cc 2 

    v, (e - b), ecc0 - bcc0, ecc1 - bcc1, ecc2 - bcc2 

[<EntryPoint>] 
let main argv = 
    let count = 10000000 

    let outers = 
    [| 
     1000 
    |] 

    for outer in outers do 
    let inner = count/outer 

    let fsImperativeTest() = 
     let mutable sum = 0 
     for n = 0 to inner-1 do 
     sum <- sum + n % 2 
     sum 

    let fsLinqTest() = 
     Enumerable.Range(0, inner).Select(fun n -> n % 2).Sum() 
    let fsNessosTest() = 
     Stream.initInfinite id 
     |> Stream.take inner 
     |> Stream.map (fun n -> n % 2) 
     |> Stream.sum 
    let fsOpTest() = 
     let remainder x = x % 2 
     let sumORemainders (input:seq<int>) = 
      let mutable sum = 0 
      use en = input.GetEnumerator() 
      while (en.MoveNext()) do 
       sum <- sum + remainder en.Current 
      sum 
     let seqb = Seq.init inner id 
     sumORemainders seqb 
    let fsSseTest() = 
     let inc   = Vector<int>.One 
     let one   = Vector<int>.One 
     let mutable sum = Vector<int>.Zero 
     let mutable n = Vector<int> [|0..3|] 
     for n4 = 0 to inner/4-1 do 
     n <- n + inc 
     sum <- sum + (n &&& one) 
     sum.[0] + sum.[1] + sum.[2] + sum.[3] 
    let fsSeqTest() = 
     Seq.init inner id 
     |> Seq.map (fun n -> n % 2) 
     |> Seq.sum 
    let fsSeqVariantTest() = 
     seq { for n = 0 to inner-1 do yield n } 
     |> Seq.map (fun n -> n % 2) 
     |> Seq.sum 

    let csImperativeTest = 
     let f = Perfs.CsImperativeTest inner 
     fun() -> f.Invoke() 
    let csLinqTest = 
     let f = Perfs.CsLinqTest inner 
     fun() -> f.Invoke() 
    let csOpTest = 
     let f = Perfs.CsOpTest inner 
     fun() -> f.Invoke() 

    let tests = 
     [| 
     "fsImperativeTest" , fsImperativeTest 
     "fsLinqTest"  , fsLinqTest 
     "fsNessosTest"  , fsNessosTest 
     "fsOpTest"   , fsOpTest 
     "fsSeqTest"   , fsSeqTest 
     "fsSeqVariantTest" , fsSeqVariantTest 
     "fsSseTest"   , fsSseTest 
     "csImperativeTest" , csImperativeTest 
     "csLinqTest"  , csLinqTest 
     "csOpTest"   , csOpTest 
     |] 

    printfn "Test run - total count: %d, outer: %d, inner: %d" count outer inner 

    for name, test in tests do 
     printfn "Running %s..." name 
     let v, ms, cc0, cc1, cc2 = time outer test 
     printfn " it took %d ms - collection count is %d,%d,%d - result is %A" ms cc0 cc1 cc2 v 


    0 

一致するC#コード:

namespace CsPerfs 
{ 
    using System; 
    using System.Collections.Generic; 
    using System.Linq; 

    public static class Perfs 
    { 
     static int Remainder(int num) 
     { 
      return num % 2; 
     } 

     static int SumOfremainders(IEnumerable<int> list) 
     { 
      var sum = 0; 
      foreach (var num in list) 
      { 
       sum += Remainder(num); 
      } 
      return sum; 
     } 

     public static Func<int> CsOpTest (int count) 
     { 
     return() => SumOfremainders (Enumerable.Range(1, count)); 
     } 

     public static Func<int> CsImperativeTest (int count) 
     { 
     return() => 
      { 
      var sum = 0; 
      for (var n = 0; n < count; ++n) 
      { 
       sum += n % 2; 
      } 
      return sum; 
      }; 
     } 

     public static Func<int> CsLinqTest (int count) 
     { 
     return() => Enumerable.Range (0, count).Select (n => n % 2).Sum(); 
     } 
    } 
} 

私のマシン(インテルCore I5)で見られるパフォーマンスの数値.NET 4.6.1 64ビット上で実行しては:

Test run - total count: 10000000, outer: 1000, inner: 10000 
Running fsImperativeTest... 
    it took 20 ms - collection count is 0,0,0 - result is 5000 
Running fsLinqTest... 
    it took 124 ms - collection count is 0,0,0 - result is 5000 
Running fsNessosTest... 
    it took 56 ms - collection count is 0,0,0 - result is 5000 
Running fsOpTest... 
    it took 1320 ms - collection count is 661,0,0 - result is 5000 
Running fsSeqTest... 
    it took 1477 ms - collection count is 661,0,0 - result is 5000 
Running fsSeqVariantTest... 
    it took 512 ms - collection count is 0,0,0 - result is 5000 
Running fsSseTest... 
    it took 2 ms - collection count is 0,0,0 - result is 5000 
Running csImperativeTest... 
    it took 19 ms - collection count is 0,0,0 - result is 5000 
Running csLinqTest... 
    it took 122 ms - collection count is 0,0,0 - result is 5000 
Running csOpTest... 
    it took 58 ms - collection count is 0,0,0 - result is 5000 
Press any key to continue . . . 

Seqはありませんメモリを消費します。 F#コードとC#コードの両方がLINQを使用する場合、期待どおりの実際の違いはありません。 NessosはF#(およびC#)のパフォーマンスデータパイプラインであり、大幅に改善されています。

forループが「ハードコーディング」されている方が良いでしょう。最も速い解決策は、SSEをSystem.Numerics.Vectorsまで使用することです。残念ながらSystem.Numerics.Vectors%をサポートしていないため、少し不公平になります。

違いはあまり言語の問題ではなく、ライブラリの問題です。

関連する問題