7
votes

HashMap avec 8 millions d'entrées devient lent

J'ai un haschmap avec une mappage de 8 millions de points2D à une liaison linked. XXX

Tout fonctionne comme il le devrait, mais il faut très longtemps pour que je reçois des données du hashmap. Y a-t-il une alternative, que je puisse utiliser pour optimiser obtenir des données de sortie?

Je suis prêt à compromettre le temps nécessaire à mettre () en faveur du temps nécessaire pour obtenez () .


16 commentaires

La vérification des collisions est toujours un bon point de départ lorsque votre hashmap est lente. La différence entre un HASHCode (CODE> HASHCODE () peut être énorme.


Probablement pas utile, mais je vais passer à une base de données. :)


Ceci ici Peut-être utile être utile: JavaSpecialists.eu/archive/issue235.html


Regarder les choses dans un hashmap devrait être rapide. Raisons possibles: point2d peut ne pas avoir de bon méthode HashCode de sorte que vous obtenez des collisions de hasch ou non la recherche de carte est lente, mais quelque chose d'autre, comme traversant un long LinkedList .


Vous pouvez envisager d'utiliser une structure de données spécifiquement conçue pour les recherches spatiales. Par exemple, si vos "clés" sont corrigées, vous pouvez utiliser un arbre K-D ou similaire.


La base de données n'est pas possible pour le moment. Tous les points du hashmap sont très proches (dans les décimales) - cela pourrait-il causer beaucoup de collisions?


Quelle est la recherche réelle que vous faites ici? Vous recherchez des points exacts ou faites-vous des recherches les plus proches?


Recherches réelles. J'ai des points et je veux trouver les bords liés aux points pour une recherche A *.


Dans un *, un cache LRU devrait aider


Je ne voudrais pas blâmer le hashmap en premier lieu. Voir Quand utiliser LinkedList Over ArrayList? ...


Je pense que cela ne pourrait pas changer de problème pour l'arrayliste, car nous y sortons tous.


@Hedam ArrayList est plus rapide pour itérer que A LinkedList .


Que diriez-vous d'utiliser un cache, comme Ehcache? ehcache.org/documentation/2.7/get-started/introduction.html < / a>


Peut-être qu'une structure de type arborescente serait utile aussi.


S'il vous plaît montrer point2d.hashscode implémentation


@FEDERICOPERALTASCAffner Implémentation par défaut


3 Réponses :


3
votes

Tout d'abord, vérifiez la fonction de hachage de votre clé (point2D). Il devrait être bien distribué ou vous aurez beaucoup de collisions. Voir la réponse de Eugene pour une explication sur la beauté qui est le hashmap.

Deuxièmement, selon la manière dont vous souhaitez accéder aux données (par lesquels je veux dire, vous essayez d'obtenir un même objet), vous pouvez utiliser un cache, Mais ce n'est que si vous ne pouvez pas modifier le hashcode code>. P>

Voici mon implémentation d'un cache LRU d'un autre projet. Cela pourrait accélérer certaines choses (ce n'est pas un code merveilleux et ne peut pas cocher la coulée, mais devrait travailler dans la plupart des cas). P>

package util;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

public class CachedHashMap<K, V> extends HashMap<K, V>{

private static final long serialVersionUID = 36215319740415714L;

private LRUCache<K, V> cache;
private LRUCache<K, Boolean> containsCache;

public CachedHashMap(int cacheSize){
    containsCache = new LRUCache<>(2*cacheSize);
    cache = new LRUCache<>(cacheSize);
}

@Override
public V put(K key, V value){

    cache.put(key, value);
    containsCache.put(key, true);
    return super.put(key, value);

}

@Override
public boolean containsKey(Object key){

    if(containsCache.containsKey(key)){
        return containsCache.get(key);
    }

    boolean contains = super.containsKey(key);

    containsCache.put((K) key, contains);

    return contains;

}

@Override
public V remove(Object key){
    containsCache.remove(key);
    cache.remove(key);
    return super.remove(key);
}

@Override
public V get(Object key){
    if(containsCache.containsKey(key)){
        if(!containsCache.get(key))
            return null;
    }

    V value = cache.get(key);
    if(value != null)
        return value;

    value = super.get(key);

    K keyCast = (K) key;
    if(value != null){
        containsCache.put(keyCast, true);
        cache.put(keyCast, value);
    }else{
        containsCache.put(keyCast, false);
    }

    return value;
}

class LRUCache<A, B> extends LinkedHashMap<A, B> {
private static final long serialVersionUID = -5958373482978477783L;

private int cacheSize;

  public LRUCache(int cacheSize) {
    super(16, 0.75f, true);
    this.cacheSize = cacheSize;
  }

  protected boolean removeEldestEntry(Map.Entry<A, B> eldest) {
    return size() >= cacheSize;
  }
}

}


11 commentaires

Et si vous recherchez beaucoup de points inexistants, un filtre de floraison peut également être utile.


Je peux vous dire que la différence entre les 8 millions de points sont dans les décimales. Est-ce que cela affectera le hashcode par défaut?


Le XOR ne signifie-t-il pas que si x et y sont égaux, le hashcode sera toujours 0?


Les coordonnées sont toujours 6.xxxx et -55.yyyyy. Si cela produit le même hashcode, je vois vraiment le problème.


Le haschmap mis en cache a rendu le cas moyen disparu à presque rien.


@Hedam Bien sûr, vous avez un cache maintenant. Mais tu caches toujours le vrai problème et c'est bon si tu es ok avec ça


@Hedam c'est génial. Mais Eugene a vraiment raison. Si la vitesse devient un problème à nouveau (comme si elle l'a fait dans le projet où j'ai utilisé ce cache). Les misses de cache seront toujours assez lentes. En tant que fonction de hachage, je suggère de ne pas inclure la partie exposante du double de votre hachage, car ils sont toujours les mêmes. Ensuite, au lieu de XOR, utilisez le nombre de nombres premiers standard.


Pourquoi faites-vous cette double recherche dans si (contient.Cache.containsikeykey (touche)) {retour contient contient.get (clé); } ? Votre contenant ne contient que les touches actuelles ou, en d'autres termes, mappe toujours sur true , alors pourquoi pas simplement si (contient. contient.Containskey (touche) {retour vrai; } ? Eh bien, ou pourquoi ne pas utiliser de définir en premier lieu?


@Hedam différence entre 8 millions de points est dans les décimales , mais plus tard, vous donnez un exemple dans lequel la précision n'est que de 4 chiffres après le point. 4 chiffres ne peuvent pas fournir 8 millions d'entrées ...


La contient contient également de faux, car lorsque la carte n'a pas la clé donnée.


@Eugene, c'était un exemple. J'utilise tous les chiffres significatifs dans le float Point2D.



8
votes

La première chose est de vérifier la distribution du hashcode. D'abord vérifier cela, mais avec un changement mineur. Le code HASHCODE d'une clé à l'intérieur d'une carte est re-hachée interne via: xxx

Vous devez donc vraiment ré-récupérez votre hashcode avec cette fonction et que plus tard vérifiez comment Il est distribué.

généralement sous une bonne distribution, votre Treenodes (Voici comment un godet est défini pour de nombreuses entrées) est très rapide à trouver. Pour un godet qui aurait INTEGER.MAX_VALUE Les entrées, il faudrait au plus tort 32 pour le trouver. Cela se produit parce que la poubelle est transformée en un arbre noir parfaitement noir .

recherche à l'intérieur d'un carte en général est O (1 ) . Recherche d'une poubelle avec Treenodes est O (logn) . Et la recherche à l'intérieur d'une liaison linked est o (n) - beaucoup pire que les précédents.

Mais c'est le temps nécessaire pour trouver un single Entrée sur la carte. Si vous devez en outre obtenir l'élément à partir de la liste qui signifie une heure supplémentaire (pire, puis trouvant l'entrée sur la carte)

aussi pour tant d'entrées qu'il est essentiel de spécifier le loadfactor et InitialCapacité initialement (au moins initialeCapacité), avant de mettre les éléments dans la carte. Ceci est dû à la rééchage et au déménagement d'éléments pour un autre seau (potentiellement). Si vous les mettez d'abord tous et que d'essayer de les trouver, sans modifier la carte - ce ne sera pas un problème lors de la recherche ...

Mais généralement à moins que vous ne mesuez et mesurez correctement, cela pourrait ne pas être le problème que vous êtes confronté. Vous pouvez regarder dans la mauvaise direction.


1 commentaires

La première chose est de vérifier est où le goulot d'étranglement réel est, par ex. en utilisant un profileur. Si je vois linkedlist dans la déclaration, je ne suis pas disposé à accepter la déclaration selon laquelle le problème hashmap est le problème, sans aucune preuve ...



2
votes

Pour prouver un point, voici un test JMH code> qui recherche une entrée dans les cartes contenant des 10, 10_000 et 10_000_000 code> entrées. Vous pouvez voir em> à partir des résultats que la recherche est constante code> - o (1). Le problème est ailleurs dans votre code. Même si vous ne comprenez pas le code, les résultats ne sont que des chiffres lisibles (voir à la fin).

  Benchmark                                          (howManyEntries)  Mode  Cnt   Score   Error  Units
hashmap8Millions.EightMillionsTest.isPresent                      1  avgt    5   8.787 ± 0.259  ns/op
hashmap8Millions.EightMillionsTest.isPresent                     10  avgt    5   9.988 ± 0.283  ns/op
hashmap8Millions.EightMillionsTest.isPresent                    256  avgt    5   9.146 ± 2.081  ns/op
hashmap8Millions.EightMillionsTest.isPresent                  10000  avgt    5   8.871 ± 0.574  ns/op
hashmap8Millions.EightMillionsTest.isPresent                1000000  avgt    5   8.894 ± 0.676  ns/op
hashmap8Millions.EightMillionsTest.isPresent               10000000  avgt    5  10.884 ± 5.623  ns/op
hashmap8Millions.EightMillionsTest.mightBePresent                 1  avgt    5   4.607 ± 0.175  ns/op
hashmap8Millions.EightMillionsTest.mightBePresent                10  avgt    5   4.713 ± 0.944  ns/op
hashmap8Millions.EightMillionsTest.mightBePresent               256  avgt    5   5.283 ± 0.511  ns/op
hashmap8Millions.EightMillionsTest.mightBePresent             10000  avgt    5   8.944 ± 0.144  ns/op
hashmap8Millions.EightMillionsTest.mightBePresent           1000000  avgt    5  10.256 ± 0.121  ns/op
hashmap8Millions.EightMillionsTest.mightBePresent          10000000  avgt    5   8.764 ± 0.504  ns/op


0 commentaires