Подтвердить что ты не робот

F # vs С# для генератора простых чисел

Я заметил, что, казалось бы, эквивалентный код в F # и С# не выполняет то же самое. F # медленнее на порядок. В качестве примера я предоставляю свой код, который генерирует простые числа /, дает nth простое число в F # и С#. Мой код F #:

let rec isprime x =
primes
|> Seq.takeWhile (fun i -> i*i <= x)
|> Seq.forall (fun i -> x%i <> 0)

and primes = 
    seq {
        yield 2
        yield! (Seq.unfold (fun i -> Some(i, i+2)) 3)
                |> Seq.filter isprime
    }


let n = 1000
let start = System.DateTime.Now
printfn "%d" (primes |> Seq.nth n)
let duration = System.DateTime.Now - start
printfn "Elapsed Time: "
System.Console.WriteLine duration

И С# выглядит так:

class Program
{
    static bool isprime(int n)
    {
        foreach (int p in primes())
        {
            if (p * p > n)
                return true;
            if (n % p == 0)
                return false;
        }
        return true;
    }

    static IEnumerable<int> primes()
    {
        yield return 2;
        for (int i=3; ; i+=2)
        {
            if (isprime(i))
                yield return i;
        }
    }

    static void Main(string[] args)
    {
        int n = 1000;
        var pr = primes().GetEnumerator();
        DateTime start = DateTime.Now;
        for (int count=0; count<n; count++)
        {
            pr.MoveNext();
        }
        Console.WriteLine(pr.Current);
        DateTime end = DateTime.Now;
        Console.WriteLine("Duration " + (end - start));
    }
}

Когда я измеряю для разных n, я получаю преимущество для С# не менее 7x следующим образом:

  • n = 100: С# = 5milsec F # = 64milsec
  • n = 1000: С# = 22milsec F # = 180milsec
  • n = 5000: С# = 280milsec F # = 2.05sec
  • n = 10000: С# = 960milsec F # = 6.95sec

Мои вопросы:

  • Являются ли эти две программы эквивалентными?
  • Если да, почему они не скомпилированы в один и тот же/эквивалентный CLI?
  • Если нет, почему бы и нет?
  • Как я могу/я могу улучшить генератор простых чисел F #, чтобы выполнить больше похожего на С#?
  • Как правило, могу ли я (или почему я не могу) всегда имитировать код С# в F #, чтобы мой код F # выполнялся одинаково быстро?

Edit1: Я понял, что сам алгоритм может быть улучшен путем перемещения только через нечетные, а не простые числа в isprime, что делает его нерекурсивным, но это своего рода перпендикулярный вопрос на заданные вопросы:)

4b9b3361

Ответ 1

Это:

Являются ли эти две программы эквивалентными?

- это немного философский вопрос.

Мне кажется, что вывод реализаций С# и F # isprime всегда будет согласован для любого заданного x, поэтому в этом смысле они эквивалентны. Однако есть много различий в том, как вы их реализовали (например, Seq.unfold создаст промежуточное значение IEnumerable<_>, тогда Seq.filter создаст еще один, так что вы создадите гораздо более короткоживущие объекты и используя намного больше вызовов функций в коде F #), поэтому совсем не удивительно, что они не эквивалентны инструкциям низкого уровня, которые генерируются соответствующими компиляторами.

Если вы хотите, вы можете создать код F #, который гораздо больше похож на код С#, за счет того, что он намного более необходим и менее идиоматичен:

let rec primes = 
    seq {
        yield 2
        let mutable x = 3
        while true do
            if isprime x then 
                yield x
            x <- x + 2
    }
and isprime x =
    use e = primes.GetEnumerator()
    let rec loop() =
        if e.MoveNext() then
            let p = e.Current
            if p * p > x then true
            elif x % p = 0 then false
            else loop()
        else true            
    loop()

primes |> Seq.item 5000 занимает примерно 0,6 с на моей машине с этой реализацией по сравнению с 2,7 с вашей реализацией. Я думаю, что в общем случае генерация кода для выражений F # seq часто немного хуже, чем у итераторов С#, поэтому я не удивлюсь, если С# еще быстрее запускается. (Но также обратите внимание, что некоторые идиомы заканчиваются быстрее в F #, чем в С#, поэтому это не тот случай, когда F # всегда медленнее - по моему опыту, два языка довольно сопоставимы в целом, и я нахожу, что код F # гораздо приятнее).

В любом случае, вместо того, чтобы вспотеть подробности о том, как сделать вывод компилятора F # более близким к компилятору С#, я бы рекомендовал искать алгоритмические улучшения. Например, простое размещение вызова Seq.cache в конце вашего первоначального определения primes делает primes |> Seq.item 5000 занять только 0,062 секунды на моей машине, что значительно быстрее, чем исходный С#.