Вопрос о производительности F #: что делает компилятор?

Ссылка на этот код: F # Статические ограничения типа пользователя

Почему, например,

let gL = G_of 1L
[1L..100000L] |> List.map (fun n -> factorize gL n)

значительно медленнее, чем

[1L..100000L] |> List.map (fun n -> factorize (G_of 1L) n)

Посмотрев на Reflector, я вижу, что компилятор обрабатывает каждую из них по-разному, но для меня слишком много делается для того, чтобы расшифровать существенную разницу. Наивно я предполагал, что первая будет работать лучше, чем позже, потому что gL предварительно вычисляется, тогда как G_of 1L должен вычисляться 100 000 раз (по крайней мере, так выглядит).

[ Edit]

Похоже, что это может быть ошибка с F # 2.0/.NET 2.0/Release-mode, см. ответ и обсуждение @gradbot.

Ответы

Ответ 1

Отражатель показывает test2(), преобразованный в 4 класса, в то время как test1() превращается в два класса. Это происходит только в режиме отладки. Отражатель показывает идентичный код (по одному классу для каждого) в режиме деблокирования. К сожалению, рефлектор падает, когда я пытаюсь просмотреть исходный код на С#, а IL действительно длинный.

let test1() =
    let gL = G_of 1L
    [1L..1000000L] |> List.map (fun n -> factorize gL n)

let test2() =
    [1L..1000000L] |> List.map (fun n -> factorize (G_of 1L) n)

Быстрый тест.

let sw = Stopwatch.StartNew()
test1() |> ignore
sw.Stop()
Console.WriteLine("test1 {0}ms", sw.ElapsedMilliseconds)

let sw2 = Stopwatch.StartNew()
test2() |> ignore
sw2.Stop()
Console.WriteLine("test2 {0}ms", sw2.ElapsedMilliseconds)

Тесты выполнялись на I7 950 @3368Mhz, Windows 7 64bit, VS2010 F # 2.0

x86 Отладка
test1 8216ms
test2 8237ms

x86 релиз
test1 6654ms
test2 6680ms

x64 Отладка
test1 10304ms
test2 10348ms

x64 релиз
test1 8858ms
test2 8977ms

Вот полный код.

open System
open System.Diagnostics

let inline zero_of (target:'a) : 'a = LanguagePrimitives.GenericZero<'a>
let inline one_of (target:'a) : 'a = LanguagePrimitives.GenericOne<'a>
let inline two_of (target:'a) : 'a = one_of(target) + one_of(target)
let inline three_of (target:'a) : 'a = two_of(target) + one_of(target)
let inline negone_of (target:'a) : 'a = zero_of(target) - one_of(target)

let inline any_of (target:'a) (x:int) : 'a =
    let one:'a = one_of target
    let zero:'a = zero_of target
    let xu = if x > 0 then 1 else -1
    let gu:'a = if x > 0 then one else zero-one

    let rec get i g = 
        if i = x then g
        else get (i+xu) (g+gu)
    get 0 zero 

type G<'a> = {
    negone:'a
    zero:'a
    one:'a
    two:'a
    three:'a
    any: int -> 'a
}    

let inline G_of (target:'a) : (G<'a>) = {
    zero = zero_of target
    one = one_of target
    two = two_of target
    three = three_of target
    negone = negone_of target
    any = any_of target
}

let inline factorizeG n = 
    let g = G_of n
    let rec factorize n j flist =  
        if n = g.one then flist 
        elif n % j = g.zero then factorize (n/j) j (j::flist) 
        else factorize n (j + g.one) (flist) 
    factorize n g.two []

let inline factorize (g:G<'a>) n =   //'
    let rec factorize n j flist =  
        if n = g.one then flist 
        elif n % j = g.zero then factorize (n/j) j (j::flist) 
        else factorize n (j + g.one) (flist) 
    factorize n g.two []

let test1() =
    let gL = G_of 1L
    [1L..100000L] |> List.map (fun n -> factorize gL n)

let test2() =
    [1L..100000L] |> List.map (fun n -> factorize (G_of 1L) n)

let sw2 = Stopwatch.StartNew()
test1() |> ignore
sw2.Stop()
Console.WriteLine("test1 {0}ms", sw2.ElapsedMilliseconds)

let sw = Stopwatch.StartNew()
test2() |> ignore
sw.Stop()
Console.WriteLine("test2 {0}ms", sw.ElapsedMilliseconds)

Console.ReadLine() |> ignore