Le but de ce prototype est de faire de la performance sur un problème assez basique. A l'origine, un problème récurrent : associer à une valeur d'Enum un texte de description. En effet, si cet enum est choisi par l'utilisateur depuis une interface, mieux vaut lui présenter quelque chose d'assez intuitif plutôt qu'un code hexadecimal.

Une solution .NET élégante consiste à mettre des attributs sur les enum. Exemple sur Code project : Mapping Text to Enum entries.

Le problème principal de cette implémentation est la performance. Elle se base sur de la reflection, et reste donce assez lente.

Le but du prototype que j'ai fait ici est de conserver cette implémentation, mais d'en augmenter les performances par divers moyens, et de mesurer cette performance.

Pour mesurer la performance, je me base sur un enum avec quatre valeurs :

   public enum MyEnum : long
   {
       [EnumName("Valeur 1")]
       EnumValue1 = 6,
       [EnumName("Valeur 2")]
       EnumValue2 = 7,
       [EnumName("Valeur 3")]
       EnumValue3 = 8,
       [EnumName("Valeur 4")]
       EnumValue4 = 9
   }

Ensuite, j'appelle pour chaque implémentation à tester la méthode de récupération de la valeur 2, et ceci 2 millions de fois. Un premier appel à la méthode est fait avant la mise en place du compteur de temps : on évite ainsi de chronométrer le premier appel, qui peut en profiter pour faire une mise en cache. Le but est donc bien de vérifier le comportement sur un grand nombre d'accès.

J'ai mis en place deux implémentations témoin :

  • Fast_Fastidious : Une implémentation "Rapide mais fastidieuse" : elle consiste à ne pas utiliser l'attribut sur l'enum, mais à implémenter à la main un select sur chaque valeur de l'enum qui renvoie la valeur correspondante. Je considère que cette implémentation est l'implémentation de référence, vers laquelle il faudra tendre au maximum en termes de performances. Son score est sur ma machine 0s203
  • Slow : Une implémentation "Lente" : Elle consiste à récupérer via reflection la valeur de l'enum, sans mise en cache ou optimisation d'aucune sorte (comme dans l'article du Code Project). Score : 30s961

Comme vous pouvez le constater, il y a un monde entre ces deux implémentations : La première est 150 fois plus rapide.

J'ai ensuite proposé trois implémentations visant à augmenter la performance de la méthode de récupération du nom de l'enum.

  • Caching : Une implémentation avec une mise en cache des résultats. Lors du premier accès à une énumération, on récupère toutes ses valeurs possibles, et on met en cache les résultats dans une table de hash. Les résultats sont bien meilleurs que dans Slow, mais loins de la cible : 2s219. La perte de performance se fait très probablement lors de l'accès aux éléments des tables de hash (deux tables de hash imbriquées : trouver d'abord l'enum, puis la valeur au sein de l'enum).
  • Emit : Une implémentation avec mise en cache également, mais au lieu de représenter les valeurs pour l'enum sous forme de table de hash, on créé dynamiquement une méthode équivalente à Fast_Fastidious à l'aide de Reflection.Emit. Cette méthode est utilisée par la suite. Reste un point de perte de performance : La sélection de la méthode en fonction de l'Enum en entrée. Ici, on se rabat sur une table de hash contenant des delegates avec les méthodes générées). Résultat : 0s797. On se rapproche.
  • Generic : Cette fois ci, je repars de Emit, et j'essaie d'améliorer les performances de la première étape : la sélection de la méthode de récupération de la description. J'utilise pour cela les Generics. En créant une classe générique avec comme type générique le type de l'énumération, cela remplace avantageusement une table de hash. Résultat : 0s359. Deux fois plus rapide que Emit mais encore deux fois plus lent que Fast_Fastidious.

On pourrait peut-être gagner un peu en performances en optimisant encore plus le code MSIL généré par Emit et Generic. J'ai fait ce code en tentant de l'optimiser autant que possible, mais je n'avais pas la documentation sur les nombres de cycles des instructions, donc cela reste approximatif. La méthode Generic reste plus restrictive que Emit, puisqu'elle impose de connaître le type de l'énumération à la compilation.

N'hésitez-pas à me dire si vous avez des idées différentes ou plus efficaces pour ce prototype, je suis intéressé.

Télécharger EnumNames