2013-08-21 2 views
13

Mon collègue et moi comparions la vitesse des fonctions C# lors du passage en lambdas pour faire du travail par rapport aux fonctions inline en ce qui concerne le temps passé à travailler. Nous avons constaté que vous avez encouru un coût lors de la transmission d'une projection lambda à une fonction de sélection C# (par exemple) et que vous vouliez voir si F # avait les mêmes problèmes, ou s'il faisait quelque chose de différent. Indépendamment de notre intention d'origine, nous avons trébuché sur quelque chose que nous ne pouvons pas comprendre. Dans l'exemple suivant, nous additionnons une liste de 3 façons différentesPourquoi réduire plus vite que sum ou sumBy?

  1. Réduire
  2. Somme
  3. SumBy

module fs 

open NUnit.Framework 
open FsUnit 
open System 
open System.Diagnostics; 

[<Test>] 
let sumTest() = 
    let nums = [0..1000] 

    let repeat = 100000 

    let stopWatch = new Stopwatch() 

    stopWatch.Start() 

    let sumsReduce = 
     [ 
      for i in [0..repeat] do 
       yield List.reduce (+) nums 
     ] 

    Console.WriteLine("reduce = {0} - Time = {1}", List.head sumsReduce, stopWatch.Elapsed.TotalSeconds); 
    stopWatch.Restart() 

    let sumsSum = 
     [ 
      for i in [0..repeat] do 
       yield List.sum nums 
     ] 

    Console.WriteLine("sum = {0} - Time = {1}", List.head sumsSum, stopWatch.Elapsed.TotalSeconds); 
    stopWatch.Restart() 


    let sumsSumBy = 
     [ 
      for i in [0..repeat] do 
       yield List.sumBy id nums 
     ] 

    Console.WriteLine("sumBy = {0} - Time = {1}", List.head sumsSumBy, stopWatch.Elapsed.TotalSeconds); 
    stopWatch.Restart() 

La sortie à cela ressemble à:

reduce = 500500 - Time = 0.2725156 
sum = 500500 - Time = 1.1183165 
sumBy = 500500 - Time = 1.1126781 

Donc clairement réduire est le grand gagnant ici. Dans la décompilation, je peux voir qui permettent de réduire obtient bouilli vers le bas

[Serializable] 
internal class sumsReduce\u004021\u002D1 : OptimizedClosures.FSharpFunc<int, int, int> 
{ 
    internal sumsReduce\u004021\u002D1() 
    { 
    base.\u002Ector(); 
    } 

    public override int Invoke(int x, int y) 
    { 
    return x + y; 
    } 
} 

Mais je vais avoir du mal à comprendre quelle somme et sumBy font. D'où vient l'écart de synchronisation?


La réponse actuelle a suggéré que réduire est 5 fois plus rapide parce que je donnais à l'origine réduire un opérateur non contrôlé. Cependant, la mise à jour du test à utiliser un opérateur contrôlé (à partir du module Checked) et je reçois toujours le même résultat

let sumsReduce = 
     [ 
      for i in [0..repeat] do 
       yield List.reduce (Checked.(+)) nums 
     ] 

Notez la différence de temps existe encore

reduce = 500500 - Time = 0.274697 
sum = 500500 - Time = 1.1126796 
sumBy = 500500 - Time = 1.1370642 
+0

Je serais très enclin à croire que @jildjarn a eu raison. L'arithmétique vérifiée doit ralentir un peu les choses. –

+0

@OnorioCatenacci, j'ai couru le test avec l'arithmétique vérifié et il n'a fait aucune différence – devshorts

Répondre

16

Sum et SumBy utilisent un énumérateur:

while e.MoveNext() do 
     acc <- Checked.(+) acc e.Current 
    acc 

alors réduire utilise une boucle récursive avec une fermeture optimisée: (réduire les utilisations plient sous le de couverture - pli f tête queue)

let fold<'T,'State> f (s:'State) (list: 'T list) = 
     match list with 
     | [] -> s 
     | _ -> 
      let f = OptimizedClosures.FSharpFunc<_,_,_>.Adapt(f) 
      let rec loop s xs = 
       match xs with 
       | [] -> s 
       | h::t -> loop (f.Invoke(s,h)) t 
      loop s list 

L'utilisation de fermetures optimisées peut souvent améliorer les performances.

+1

Hmm cela a plus de sens, mais dans ce cas, la fonction SumLoop dans l'essentiel devrait fonctionner plus vite que SumEnum. https://gist.github.com/faisalmansoor/6301115 Mais, List.reduce se résume à SumLoop comme méthode, tandis que List.Sum à SumEnum. Des idées? –

+1

Je pense que c'est la traversée de liste qui fait la différence, pas le 'OptimizedClosure'. L'utilisation d'un 'OptimizedClosure' et d'un énumérateur offre très peu d'augmentation des performances, mais l'utilisation de' Checked. (+) 'Dans une fonction récursive est tout aussi rapide que' List.Reduce'. –

+0

Certes, la fermeture optimisée fait plus de différence avec les fonctions fortement curry etc. – 7sharp9

7

sum et sumBy utilisation contrôlée arithmétique, mais vous passez l'opérateur non contrôlé + à reduce - pas exactement des pommes aux pommes.

+0

Eh bien, cela a du sens. Merci pour l'info – devshorts

+1

Ok, donc j'ai fait un nouveau test et je ne pense pas que c'est ça. Je vais mettre à jour la question – devshorts

Questions connexes