Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why are F# list ranges so much slower than for loops?

Tags:

list

f#

I'm surprised how much slower the List range is for the example below. On my machine the for loop is a factor of 8 or so quicker.

Is an actual list of 10,000,000 elements created first? And if so, is there a reason (other than it has not been done yet) why this can't be optimised away by the compiler?

open System
open System.Diagnostics

let timeFunction f v =
    let sw = Stopwatch.StartNew()
    let result = f v
    sw.ElapsedMilliseconds

let length = 10000000

let doSomething n =
    (float n) ** 0.1 |> ignore

let listIter n =
    [1..length] |> List.iter (fun x -> doSomething (x+n))

let forLoop n = 
    for x = 1 to length do
        doSomething (x+n)

printf "listIter   : %d\n" (timeFunction listIter 1)  // c50
GC.Collect()
printf "forLoop    : %d\n" (timeFunction forLoop 1)  // c1000
GC.Collect()
like image 813
Mark Pattison Avatar asked Oct 11 '12 14:10

Mark Pattison


People also ask

Why is the F-16 so popular?

The F-16 Fighting Falcon is a compact, multi-role fighter aircraft. It is highly maneuverable and has proven itself in air-to-air combat and air-to-surface attack. It provides a relatively low-cost, high-performance weapon system for the United States and allied nations.

Why are guitar holes F shaped?

Simply: it's a shape that is cut out from the body of the guitar (or other instrument) that affects the tone and sound that is produced when it is played. That sound is different to regular guitars that don't have f-holes and is desirable for specific types and genres of music.

Why is the F 22 being phased out?

Assistant Budget Secretary Major General James Peccia subsequently highlighted in March 2022 that the F-22's extreme maintenance costs, as well as the very high costs of upgrading the airframes, made it favourable to begin retiring Raptors in 2023.

Has an F 22 ever been shot down?

In aerial combat, it has a staggering 100 confirmed kills and zero losses. In an earlier report by The EurAsian Times, a USAF pilot had admitted that the 'mighty' F-22 Raptors would avoid a dogfight with Sukhoi Su-35 jets and instead call on the F-15 fighters to tackle the Russian threats.


2 Answers

Using ILSpy, listIter looks like this:

public static void listIter(int n)
{
    ListModule.Iterate<int>(
        new listIter@17(n), 
        SeqModule.ToList<int>(
            Operators.CreateSequence<int>(
                Operators.OperatorIntrinsics.RangeInt32(1, 1, 10000000)
            )
        )
    );
}

Here are the basic steps involved:

  1. RangeInt32 creates an IEnumerable (which is inexplicably wrapped by CreateSequence)
  2. SeqModule.ToList builds a list from that sequence
  3. An instance of listIter@17 (your lambda) is new'd up
  4. ListModule.Iterate traverses the list calling the lambda for each element

vs forLoop, which doesn't look much different from what you've written:

public static void forLoop(int n)
{
    for (int x = 1; x < 10000001; x++)
    {
        int num = x + n;
        double num2 = Math.Pow((double)num, 0.1);
    }
}

...no IEnumerable, lambda (it's automatically inlined), or list creation. There's a potentially significant difference in the amount of work being done.

EDIT

For curiosity's sake, here are FSI timings for list, seq, and for loop versions:

listIter - Real: 00:00:03.889, CPU: 00:00:04.680, GC gen0: 57, gen1: 51, gen2: 6  
seqIter  - Real: 00:00:01.340, CPU: 00:00:01.341, GC gen0:  0, gen1:  0, gen2: 0  
forLoop  - Real: 00:00:00.565, CPU: 00:00:00.561, GC gen0:  0, gen1:  0, gen2: 0

and the seq version for reference:

let seqIter n =
    {1..length} |> Seq.iter (fun x -> doSomething (x+n))
like image 128
Daniel Avatar answered Oct 29 '22 07:10

Daniel


Using {1..length} |> Seq.iter is certainly faster as you don't create the full list in memory.

Another slightly faster way than your for loop is:

let reclist n =
    let rec downrec x n =
        match x with 
        | 0 -> ()
        | x -> doSomething (x+n); downrec (x-1) n
    downrec length n

Interesting is that the code for the recursive function boils down to:

while (true)
{
    switch (x)
    {
    case 0:
        return;
    default:
    {
        int num = x + n;
        double num2 = Math.Pow((double)num, 0.1);
        int arg_26_0 = x - 1;
        n = n;
        x = arg_26_0;
        break;
    }
    }
}

Even when using optimization, there are still a few lines that could have been removed, i.e to this:

while (true)
{
    switch (x)
    {
    case 0:
        return;
    default:
    {
        int num = x + n;
        double num2 = Math.Pow((double)num, 0.1);
        x = x - 1;
        break;
    }
    }
}
like image 40
Storstamp Avatar answered Oct 29 '22 05:10

Storstamp