java - immutable map of
Guava ImmutableMap tiene un acceso notablemente más lento que HashMap (2)
Algunas posibles razones:
¿Podría eso depender de su implementación de RndString.build ()?
Y eche un vistazo a la implementación get () de ambos mapas: com.google.common.collect.RegularImmutableMap.get (Object) java.util.HashMap.getEntry (Object) java.util.HashMap intenta comparar con "== " primero. RegularImmutableMap no lo hace. Eso puede acelerar
¿Podría un factor de carga diferente ser responsable de eso? Quizás RegularImmutableMap necesite más iteraciones para encontrar la entrada correcta.
Mientras trabajaba en un benchmark de memoria de algunas estructuras de datos de alto rendimiento, me di cuenta de que podía usar un ImmutableMap
con solo un poco de refactorización.
Pensando que esto sería una mejora, lo lancé a la mezcla y me sorprendí al descubrir que no solo era más lento que HashMap
, en un entorno de subproceso único, ¡parece ser consistentemente más lento incluso que ConcurrentHashMap
!
Puede ver el punto de referencia completo aquí: https://bitbucket.org/snippets/dimo414/89K7G
La carne de la prueba es bastante simple, el tiempo que toma obtener una gran cantidad de cadenas aleatorias que pueden existir en el mapa.
public static void timeAccess(Map<String,String> map) {
Random rnd = new Random(seed);
int foundCount = 0;
long start = System.nanoTime();
for(int i = 0; i < loop; i++) {
String s = map.get(RndString.build(rnd));
if(s != null)
foundCount++;
}
long stop = System.nanoTime() - start;
System.out.println("Found "+foundCount+" strings out of "+loop+" attempts - "+
String.format("%.2f",100.0*foundCount/loop)+" success rate.");
System.out.println(map.getClass().getSimpleName()+" took "+
String.format("%.4f", stop/1_000_000_000.0)+" seconds.");
System.out.println();
}
Y al ejecutar esto contra un HashMap
, un ConcurrentHashMap
y un ImmutableMap
, todos con los mismos valores, mostraron una dramática desaceleración al usar ImmutableMap
, a menudo más del 15% más lento. Cuanto más disperso es el mapa (es decir, cuanto más a menudo map.get()
devuelve nulo), mayor es la disparidad. Este es el resultado de una ejecución de muestra:
Found 35312152 strings out of 100000000 attempts - 35.31 success rate.
HashMap took 29.4538 seconds.
Found 35312152 strings out of 100000000 attempts - 35.31 success rate.
ConcurrentHashMap took 32.1465 seconds.
Found 35312152 strings out of 100000000 attempts - 35.31 success rate.
RegularImmutableMap took 37.9709 seconds.
¿Es esto un problema documentado / esperado? Los documentos de Guava indican que Immutable***
es más eficiente en cuanto a la memoria, pero no dice nada sobre la velocidad. Para desaceleraciones de esta magnitud, me inclino a lidiar con los costos de la memoria y evitar el Immutable***
cuando la velocidad es un problema (¿y cuándo no ?!). ¿Me estoy perdiendo de algo?
Ver también: https://groups.google.com/forum/?fromgroups=#!topic/guava-discuss/I7yPpa5Hlpg
Como dijo Louis Wasserman, ImmutableMap
no está optimizado para objetos con método lento equals
. Creo que la principal diferencia está aquí:
HashMap :
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
if (key.equals(candidateKey)) {
return entry.getValue();
Como puede ver, para verificar colisiones, HashMap
primero verifica los valores hash. Esto permite rechazar valores con hashes diferentes rápidamente. Como String
no hace esta comprobación en su método equals
, esto hace que HashMap
sea más rápido. ImmutableMap
no usa esta optimización porque haría la prueba más lenta cuando los equals
ya están optimizados.