2013-06-04 1 views
2

Je modélise certaines classes pour représenter les unités de mesure en C#. Par exemple, j'ai des Millimètres et des Pouces modélisés, avec une interface IDistanceUnit et une classe de base DistanceUnit fournissant des détails d'implémentation communs.Polymorphic Performance Hit

Il y a des fonctions arithmétiques de base, définis comme tels, dans chacune des classes concrètes:

public class Inches : 
    DistanceUnit<Inches>, 
    IDistanceUnit, 
    INumericUnit<Inches, IDistanceUnit> 
{ 
    // ... snip ... 
    public Inches Add(IDistanceUnit unit) 
    { 
     return new Inches(Value + unit.ToInches().Value); 
    } 
    // ... snip ... 
} 

Analyse comparative 10000000 additions de différentes valeurs converties en pouces et Millimètres a un petit mais a frappé acceptable de performance. Les doubles bruts effectuant la conversion manuellement prennent environ 70-100ms, où les classes prennent environ 700-1000ms.

je peux abstraire les détails vers le bas dans la classe de base DistanceUnit et supprimer certains chevauchements inutiles:

public abstract class DistanceUnit<TConcrete> 
     IDistanceUnit, 
     INumericUnit<TConcrete, IDistanceUnit> 
    where TConcrete : 
     IDistanceUnit, 
     INumericUnit<TConcrete, IDistanceUnit>, 
     new() 
{ 
    // ... snip ... 
    public TConcrete Add(IDistanceUnit unit) 
    { 
     TConcrete result = new TConcrete(); 
     reuslt.Value = Value + ToThis(unit).Value(); 
     return result; 
    } 
    // ... snip ... 
} 
// ToThis() calls the correct "ToXYZ()" for the current implementing class 

Cette baisse ma performance par au moins un autre facteur de 10. Je vois autour de 8000ms, juste de déplacer l'implémentation dans la classe de base, par rapport à 800ms.

Je l'ai déclaré à quelques choses de tests manuels:

  • Le passage à l'aide ToThis(IDistanceUnit) montre aucune performance notable a frappé quand il est utilisé dans la classe concrète au lieu de l'appel directe aux « ToInches "ou « ToMillimeters »
  • Création de l'objet avec un constructeur parameterless (new TConcrete()) et l'attribution d' la valeur a plus tard au pire un millisecondes couple de perte de performance sur 10.000.000 cal culations.

J'utilise le code suivant pour comparer mes résultats:

int size = 10000000; 
double[] mms = new double[size]; 
double[] inches = new double[size]; 

// Fill in some arbitrary test values 
for (int i = 0; i < size; i++) 
{ 
    mms[i] = i; 
    inches[i] = i; 
} 

Benchmark("Manual Conversion",() => 
{ 
    for (int i = 0; i < size; i++) 
    { 
     var result = mms[i] + (inches[i] * 25.4); 
    } 
}); 

Benchmark("Unit Classes",() => 
{ 
    for (int i = 0; i < size; i++) 
    { 
     var result = (new Millimeters(mms[i])) + (new Inches(inches[i])); 
    } 
} 

Où référence appelle juste un chronomètre autour de l'action prévue et imprime le temps écoulé en millisecondes. La seule chose qui semble causer une différence majeure est le mouvement de la fonction Add des classes concrètes à la classe de base abstraite, mais je ne comprends pas pourquoi j'aurais une baisse de performance de près de 10x . Quelqu'un peut-il m'expliquer cela (et si possible, suggérer un moyen d'obtenir une meilleure vitesse sans recourir à la duplication de code)?

+1

Je ne sais pas C#, mais si je devais deviner, il semble comme l'appel polymorphique n'est pas en ligne. – Mysticial

+0

@Mystical Aww man, j'espère que ce n'est pas le cas. Juste fait une vérification rapide, les fonctions en ligne (en dehors de ce que le compilateur fait par lui-même) ne sont que dans 4.5 et plus, et je suis coincé dans 4.0 et ci-dessous. – KChaloux

+0

Le compilateur JIT pouvait toujours intégrer des fonctions, ce qui n'est pas nouveau dans la version 4.5 - seule la possibilité de demander une ligne de commande agressive est nouvelle. – harold

Répondre

3

Pour le premier coup de perfomance comme je l'ai dit dans les commentaires, j'ai besoin de connaître quelques détails d'implémentation: Méthode ToInches(), méthode ToThis(), le type ou le code de la propriété Value.

Pour la deuxième perfomance a frappé, la raison la plus probable est l'utilisation nouvelle() contraindre la classe de base et utiliser
TConcrete result = new TConcrete();

compilateur génère Activator.CreateInstance<TConcrete>() dans ce cas, cette méthode utilise la réflexion sous le capot et est lent. Dans le cas où vous avez des millions d'instanciations, cela diminuera la performance à coup sûr. Here vous pouvez trouver quelques repères de Jon Skeet.

Si vous utilisez> = 3.5 cadre, vous pouvez utiliser les arbres d'expression pour construire instanciation générique rapide des objets dans votre classe de base:

private static Func<TConcrete> _createFunc; 

    private static TConcrete CreateNew() 
    { 
      if (_func == null) 
      { 
       _createFunc = Expression.Lambda<Func<TConcrete>>(Expression.New(typeof (TConcrete))).Compile(); 
      } 
      return _createFunc.Invoke(); 
    } 

    public TConcrete Add(IDistanceUnit unit) 
    { 
      TConcrete result = CreateNew(); 
      result.Value = Value + ToThis(unit).Value(); 
      return result; 
    } 
+0

Malheureusement, je suis coincé supportant des choses aussi bas que. NET 2.0, mais la révélation quant au fait que 'new()' utilise 'Activator.CreateInstance' est très utile. Merci. – KChaloux