web-dev-qa-db-fra.com

Performances des instructions HashMap vs Switch

Un HashMap a essentiellement les performances de O(1) alors qu'un état de commutateur peut avoir O(1) ou O(log(n)) selon que le compilateur utilise un tableswitch ou une recherche. commutateur.

Naturellement, si une instruction switch est écrite en tant que telle,

switch (int) {
    case 1:
    case 2:
    case 3:
    case 4:
    default:
}

il utiliserait alors un tableswitch et aurait clairement un avantage en performances par rapport à un HashMap standard. Mais que se passe-t-il si l'instruction switch est rare? Ce sont deux exemples que je comparerais:

HashMap<Integer, String> example = new HashMap<Integer, String>() {{
        put(1, "a");
        put(10, "b");
        put(100, "c");
        put(1000, "d");
}};

.

switch (int) {
    case 1:
        return "a";
    case 10:
        return "b";
    case 100:
        return "c";
    case 1000:
        return "d";
    default:
        return null;
}

Qu'est-ce qui fournirait plus de débit, un commutateur de recherche ou une HashMap? La surcharge de HashMap donne-t-elle un avantage à la recherche, mais finit par diminuer à mesure que le nombre de cas/d'entrées augmente?

Edit: J'ai essayé quelques tests en utilisant JMH, voici mes résultats et le code utilisé. https://Gist.github.com/mooman219/bebbdc047889c7cfe612 Comme vous l'avez mentionné, la déclaration lookupswitch a surpassé la table de hachage. Je me demande encore pourquoi.

20
Joe C

Ça dépend:

  1. S'il y a quelques articles | éléments fixes. Utiliser switch si vous le pouvez (pire des cas, O (n))

  2. S'il y a beaucoup d'éléments OR, vous souhaitez ajouter des éléments futurs sans modifier beaucoup de code ---> Utilisation de hash-map (le temps d'accès est considéré comme un temps constant)

  3. Pour votre cas. Ne vous inquiétez pas pour les performances car le temps d'exécution différent est très petit. Concentrez-vous simplement sur la lisibilité/la maintenabilité de votre code. Vaut-il la peine d'optimiser un cas simple pour améliorer quelques nanosecondes?

22
Loc

La réponse acceptée est fausse ici.

http://Java-performance.info/string-switch-implementation/

Les commutateurs seront toujours aussi rapides que s'ils n'étaient pas plus rapides que les cartes de hachage. Les instructions de commutateur sont transformées en tables de recherche directes. Dans le cas des valeurs Integer (ints, enums, shorts, long), il s'agit d'une recherche directe/jmp de l'instruction. Il n'y a pas de hachage supplémentaire à faire. Dans le cas d'une chaîne, il précalcule le hachage de la chaîne pour les instructions case et utilise le hashcode de la chaîne en entrée pour déterminer où sauter. En cas de collision, il effectue une chaîne if/else. Maintenant, vous pourriez penser "Ceci est la même chose que HashMap, non?" Mais ce n'est pas vrai. Le code de hachage pour la recherche est calculé lors de la compilation et il n'est pas réduit en fonction du nombre d'éléments (moindre risque de collision).

Les commutateurs ont une recherche O(1) et non pas O (n). (Ok, en réalité, pour un petit nombre d'éléments, les commutateurs sont transformés en instructions if/else. Cela fournit une meilleure localisation du code et évite des recherches supplémentaires dans la mémoire. Cependant, pour de nombreux éléments, les commutateurs sont modifiés dans la table de recherche susmentionnée.

Vous pouvez en lire plus à ce sujet ici Comment fonctionne le commutateur Java sous le capot?

6
Cogman

Dans votre cas, puisque vous utilisez une clé Integer pour votre HashMap et un simple 'int' pour votre instruction switch, la meilleure implémentation sera celle switch, sauf si le nombre de passages dans cette section de code est très élevé des centaines de milliers).

0
sqlsword

Si j'ai ce genre d'exemple, j'utilise Guava ImmutableMaps (bien sûr, vous pouvez également utiliser Java 9 Builder).

private static final Map<String, String> EXAMPLE = ImmutableMaps.<String, String>>builder()
    .put("a", "100")
    .put("b", "200")
    .build(); 

De cette façon, ils sont immuables et initiés une seule fois. 

Parfois, j'utilise un modèle de stratégie de cette façon:

private static final Map<String, Command> EXAMPLE = ImmutableMaps.<String, String>>builder()
    .put("a", new SomethingCool())
    .put("b", new BCool())
    .build(); 

private static final Command DEFAULT= new DefCommand();

Utilisation:

EXAMPLE.getOrDefault("a", DEFAULT).execute(); //Java 8

À propos des performances, choisissez simplement la lisibilité. Vous me remercierez plus tard (1 an plus tard): D. 

0
John Tribe