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)?
Je ne sais pas C#, mais si je devais deviner, il semble comme l'appel polymorphique n'est pas en ligne. – Mysticial
@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
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