The main reason the OP sees a performance difference is because Seq.init in F # is slow. The reason for this is that at each iteration, Seq.upto (which uses Seq.init ) allocates a new Lazy<_> object. You can see it in Seq source . If you have a low utility function, for example fun n -> n % 2 , the cost of a new Lazy<_> object, as well as its evaluation (locking and unlocking mutexes) takes a considerable amount of time.
The second reason OP sees performance differences is because Seq in F # is generally slow. This is fixed by manofstick in this PR
With PR inplace F # Seq will work very well compared to alternatives that exist (some details here )
With all of this in mind, I prepared several comparisons of the various ways the user posted calculations (other than the obvious 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
Corresponding C # code:
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 (); } } }
Performance indicators on my computer (Intel Core I5) running on .NET 4.6.1 64bit:
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 does the worst and also consumes memory. If both the F # and C # codes use LINQ, then there is no real difference as expected. Nessos is a high-performance data pipeline for F # (and C #), which is significantly better.
The hard coding for loop makes everything better, and the fastest solution is to use SSE through System.Numerics.Vectors . Unfortunately, System.Numerics.Vectors does not support % , which makes the comparison a little unfair.
Thus, the difference lies not so much in the language problem as in the problem with the library.