Функциональный стиль F# гораздо медленнее

Попытка выучить F#, решая некоторые задачи программирования. Я не хочу добавлять слишком много подробностей о проблеме, так как я не хочу портить удовольствие другим.

По сути, проблема состоит в том, чтобы найти все 4-uples { (i,j,k,l) | i ^ j ^ k ^ l!= 0 } без повторений (например, (1,1,1,2) и (1,1,2,1) одинаковы и должны учитываться только один раз).

Я нашел подход O(n^3), который работает, пожалуйста, смотрите countImperative(a,b,c,d) ниже. Но я также попытался изменить код, чтобы избавиться от вложенных циклов for. Однако я не смог бы сделать это без существенного снижения производительности. У меня сложилось впечатление, что синтаксический сахар F# позволит использовать более лаконичный стиль (с использованием конвейеров и сгибов), позволяя компилятору выполнять тяжелую работу для создания сравнительно быстрого кода (по сравнению с моим вложенным циклом). Большой удар по производительности происходит из расчета partial2 сумма.

Вот код:

open System
open System.Diagnostics
open System.Collections

module quadruples =
    [<EntryPoint>]
    let main argv = 
        let input = "2000 2000 2000 2000"
        let ordered = [ for x in input.Split([|' '|]) -> Convert.ToInt32(x) ] |> List.sort
        let a,b,c,d = ordered.[0], ordered.[1], ordered.[2], ordered.[3]
        let inner(a,b) = a * (a-1) / 2 + a * (b-a) 
        let sw = new Stopwatch()

        sw.Start()
        let partial1 = [ 1.. b ] |> List.fold (fun acc j -> acc + (int64 ((min a j) * inner(c-j+1, d-j+1)))) 0L
        sw.Stop()
        let elapsed1 = (sw.ElapsedMilliseconds |> double) / 1000.0
        printfn "Partial1: %f s" elapsed1

        sw.Restart()
        let combinations = [ for i in 1..a do for j in i+1..b do yield (j,i^^^j) ]
        let range = [ 1..c ]
        let partial2 = combinations |> List.fold(fun acc (j,x) -> acc + (range |> List.skip(j-1) |> List.fold(fun acc k -> if k ^^^ x < k || k ^^^ x > d then acc + 1L else acc) 0L)) 0L
        sw.Stop()
        let elapsed2 = (sw.ElapsedMilliseconds |> double) / 1000.0
        printfn "Partial2: %f s" elapsed2
        printfn "Functional: %d, Elapsed: %f s" (partial1 + partial2) (elapsed1 + elapsed2)

        // "imperative" approach
        let countImperative(a,b,c,d) =
            let mutable count = seq { 1..b } |> Seq.fold (fun acc j -> acc + (int64 ((min a j) * inner(c-j+1, d-j+1)))) 0L
            for i in 1..a do 
                for j in i+1..b do 
                    let x = i ^^^ j
                    for k in j..c do
                        let y = x ^^^ k
                        if y < k || y > d then
                            count <- count + 1L
            count

        sw.Restart();                        
        let count = countImperative(a,b,c,d)
        sw.Stop()
        printfn "Imperative: %d, Elapsed: %f s" count ((sw.ElapsedMilliseconds |> double) / 1000.0)
        0 // return an integer exit code

Поэтому мой вопрос был, есть ли способ ускорить код (в частности, расчет partial2), сохраняя хороший синтаксис F#.

0 ответов

Другие вопросы по тегам