Performances énumération Java

De EjnTricks
Révision de 1 octobre 2018 à 12:23 par Etienne (discussion | contributions)

(diff) ← Version précédente | Voir la version courante (diff) | Version suivante → (diff)

Les énumérations peuvent être utilisées pour gérer des dictionnaires de constantes par exemple. Cet article présente une étude sur les performances de recherche au sein d'une énumération.

Tout le code est disponible ici : http://www.svn.jouvinio.net/study/trunk/java-performance/

Hand-icon.png Votre avis

Nobody voted on this yet

 You need to enable JavaScript to vote


Study icon.png Etude

L'objectif est de rechercher au sein d'une énumération l'instance correspondant à un label. Les énumérations mises en place comportent toutes des instances avec une valeur de type String correspondant à un chiffre comme le cas de la classe fr.ejn.tutorial.java.performances.SimpleEnum.

package fr.ejn.tutorial.java.performances;

/**
 * Enumeration with search on values.
 *
 * @author Etienne Jouvin
 *
 */
public enum SimpleEnum {

  EIGHT("8"), FIVE("5"), FOUR("4"), NINE("9"), ONE("1"), SEVEN("7"), SIX("6"), THREE("3"), TWO("2");

  public static SimpleEnum getByNumber(String number) {
    SimpleEnum res = null;
    for (SimpleEnum value : SimpleEnum.values()) {
      if (number.equals(value.getNumber())) {
        res = value;
        break;
      }
    }
    return res;
  }

  private String number;

  private SimpleEnum(String number) {
    this.number = number;
  }

  public String getNumber() {
    return this.number;
  }
}


Process-icon.png Mise en place

Afin de bien étudier l'impact sur les performances, deux groupes d'énumération sont mis en place. Le premier, classes SimpleEnum / EnumWithArrayCache / EnumWithMapCache, contient tous les chiffres alors que le second groupe, classes SmallSimpleEnum / SmallEnumWithArrayCache / SmallEnumWithMapCache, ne contient que trois chiffres.

Pour les deux classes SimpleEnum et SmallSimpleEnum, la recherche s'effectue en parcourant l'ensemble des instances de l'énumération et d'identifier la cible en comparant sur la variable interne number, comme l'indique l'extrait de la classe SmallSimpleEnum.

  public static SmallSimpleEnum getByNumber(String number) {
    SmallSimpleEnum res = null;
    for (SmallSimpleEnum value : SmallSimpleEnum.values()) {
      if (number.equals(value.getNumber())) {
        res = value;
        break;
      }
    }
    return res;
  }

Pour les classes EnumWithArrayCache et SmallEnumWithArrayCache, un tableau static est initialisé avec le resultat de la fonction values. Ainsi, celle-ci ne sera pas exécutée lors de la recherche. Un gain est attendu car la valeur de values ne peut pas être modifiée. L'intialisation et la recherche s'effectue comme dans l'extrait de la classe EnumWithArrayCache.

  EIGHT("8"), FIVE("5"), FOUR("4"), NINE("9"), ONE("1"), SEVEN("7"), SIX("6"), THREE("3"), TWO("2");

  private static final EnumWithArrayCache[] VALUES = EnumWithArrayCache.values();

  public static EnumWithArrayCache getByNumber(String number) {
    EnumWithArrayCache res = null;
    for (EnumWithArrayCache value : VALUES) {
      if (number.equals(value.getNumber())) {
        res = value;
        break;
      }
    }
    return res;
  }

Enfin, pour les classes EnumWithMapCache et SmallEnumWithMapCache, une Map static est initialisée avec pour clé la valeur des instances, et pour valeur les instances. Cette implémentation va permettre de valider les performances de recherche dans une Map par rapport à un tableau. L'intialisation et la recherche s'effectue comme dans l'extrait de la classe EnumWithMapCache.

  EIGHT("8"), FIVE("5"), FOUR("4"), NINE("9"), ONE("1"), SEVEN("7"), SIX("6"), THREE("3"), TWO("2");

  private static final Map<String, EnumWithMapCache> VALUES = new HashMap<>();
  static {
    for (EnumWithMapCache value : EnumWithMapCache.values()) {
      VALUES.put(value.getNumber(), value);
    }
  }

  public static EnumWithMapCache getByNumber(String number) {
    return VALUES.get(number);
  }


Start-icon.png Exécution

Les temps d'exécution sont calculés au moyen des tests unitaires, dans la classe fr.ejn.tutorial.java.performances.EnumPerformancesTest. Le temps est mesuré à l'aide de la classe StopWatch, disponible dans la librairie commons-lang3, qui offre des facilités de chronométrage. Les résultats sont ensuite affichés à l'aide de Slf4J et de son implémentaion slf4j-simple.

Pour chacun des tests, l'énumération est chargée, afin de s'affranchir des temps d'initialiation, puis une boucle sur 3 000 000 de répétitions est réalisée pour recherche les instances sur chacun des chiffres. Par exemple, la fonction de test pour la classe SimpleEnum est la suivante.

  @Test
  public void testPerfSimpleEnum() {

    StopWatch watch = new StopWatch();
    watch.start();
    for (int cpt = 0; cpt < MAX_INTERATIONS; cpt++) {
      SimpleEnum.getByNumber("1");
      SimpleEnum.getByNumber("2");
      SimpleEnum.getByNumber("3");
      SimpleEnum.getByNumber("4");
      SimpleEnum.getByNumber("5");
      SimpleEnum.getByNumber("6");
      SimpleEnum.getByNumber("7");
      SimpleEnum.getByNumber("8");
      SimpleEnum.getByNumber("9");
    }
    watch.stop();

    LOGGER.info("Time to execute for SimpleEnum " + watch.getTime(TimeUnit.MILLISECONDS));
  }

Attention, les temps d'exécution varient forcément en fonction des machines et ressources nécessaires. Un exemple d'exécution donne les temps suivants.

  • SimpleEnum : 1216 ms
  • EnumWithArrayCache : 582 ms
  • EnumWithMapCache : 147 ms
  • SmallSimpleEnum : 150 ms
  • SmallEnumWithArrayCache : 77 ms
  • SmallEnumWithMapCache : 56 ms


Warning-icon.png Conclusion

Pour les deux groupes, il est constaté qu'il est préférable d'initialiser une variable static et de recherche sur celle-ci. La fonction values semble être couteuse.

Toutefois, cette variable devra être affinée en fonction des besoins, et ne pas oublier que le test est réalisé sur 3 000 000 d'itérations.