2010-07-25 12 views
6

He encontrado un problema que no puedo encontrar una solución. Estoy usando un HashSet para almacenar valores. Los valores que almaceno son del tipo personalizado Cycles donde he reemplazado el HashCode y es igual a lo siguiente para asegurarme de que el código lento o los métodos iguales no compliquen el rendimiento lento También he establecido la capacidad inicial del hashset para 10.000.000HashSet. rendimiento lento en el conjunto grande

@Override 
public int hashCode() { 
final int prime = 31; 
int result = 1; 
result = prime * result + (int) (cycleId^(cycleId >>> 32)); 
return result; 
} 

@Override 
public boolean equals(Object obj) { 
if (this == obj) 
return true; 
if (obj == null) 
return false; 
if (getClass() != obj.getClass()) 
return false; 
Cycle other = (Cycle) obj; 
if (cycleId != other.cycleId) 
return false; 
return true; 
} 

Después de los primeros 1.500.000 primeros valores cuando trato de añadir un nuevo valor (con el método Add de la clase HashSet) el programa es muy lento. Eventualmente voy a tener la excepción java de memoria (Excepción en el hilo "Thread-0" java.lang.OutOfMemoryError: espacio de pila de Java) antes de que los valores almacenados lleguen al 1.600.000

El IDE que uso es Eclipse. Así que el siguiente paso fue aumentar el tamaño del almacenamiento dinámico de JVM del valor predeterminado a 1 giga (usando los comandos Xmx1000M y Xms1000M) Ahora el elipse comienza con 10 veces más memoria disponible (puedo ver que en la parte inferior derecha donde está el montón total) Se muestra la memoria de tamaño y la memoria utilizada) pero, una vez más, tengo el mismo rendimiento "lento" y el mismo error de memoria EN LOS MISMOS VALORES que antes (después de los 1.500.000 y antes de 1.600.000), lo cual es muy extraño.

¿Alguien tiene una idea de lo que podría ser el problema?

Gracias de antemano

+1

¿Qué es cycleId exactamente? Si se trata de una identificación como identidad y, por lo tanto, única para los ciclos, simplemente devuelva cycleId como código hash. Si no es un Entero, entonces tome hashCode de qué tipo es. Si es un bit de 64 bits y el ID está comenzando desde 0 (con una distribución par o la mayoría en los 32 bits inferiores), entonces empújelo a int. –

+0

@lasseespeholt, ¿por qué? ¡Entonces el hashcode solo dependería de los 32 bits más bajos del largo! Usar * todos * los bits es el camino a seguir. ¡Imagine qué tipo de desastre ocurriría si String.hashCode() utilizara solo los últimos dos caracteres para hacer un 32 hashCode! –

+1

¿Ha perfilado su programa para verificar que es el 'HashSet' el que está ralentizando las cosas? –

Respuesta

10

Usted no quiere aumentar el almacenamiento dinámico de JVM para Eclipse, que desea establecer para su programa.

Ir a Ejecutar> Ejecutar configuraciones (o de depuración Configuraciones) y establecer las opciones de VM allí.

0

Tal vez su equipo no tiene suficiente memoria, por lo tanto, tiene que cambiar de disco.

2

El tamaño de memoria disponible para la aplicación que inicia desde Eclipse se debe configurar desde el menú Ejecutar. Proveedores:

Run -> Run Configurations -> Arguments -> VM Arguments -> -Xmx1000M

La razón por la que su programa es lento es recolector de basura - que se inicia cada vez que una memoria que va a estar fuera del límite.

+0

configuré la VM en 1G y solo usa menos de 100M. el programa se comporta de la misma manera con una VM de 50 M o una VM de 1G. Es bastante extraño que tenga un error de memoria llena cuando el conjunto tiene aproximadamente 1500,000 y 1,600,000 elementos independientemente de la cantidad de memoria que haya establecido. La memoria de tamaño de pila en Eclipse se muestra en la esquina inferior derecha, así que he verificado que los comandos Xmx y Xms funcionan correctamente. – Pitelk

+0

Gracias Vitalii. Revisé el jconsol y resultó que estás 100% en lo cierto. El programa usa toda la memoria mientras que eclipse muestra solo 50MB de los 1000MB. Además, Andreas tiene razón ... tengo que declarar explícitamente a la configuración del almuerzo que quiero que el programa use todo el espacio de montón independientemente del hecho de que haya comenzado el eclipse con un montón de 1000M. Gracias por su ayuda – Pitelk

1

Si desea aumentar la memoria que su programa puede usar, no ayudará a aumentar el tamaño de pila de Eclipse. Debe poner el parámetro en los parámetros vm de la configuración de inicio de su programa.

+0

Gracias Andreas! tienes razón sobre eso ... tengo que declarar explícitamente a la configuración del almuerzo que quiero que el programa use todo el espacio del montón independientemente del hecho de que haya comenzado el eclipse con un montón de 1000M. Gracias por su ayuda – Pitelk

2

¿Has probado la implementación del método hashCode? siempre devuelve 31, para cualquier valor de circleId. No es extraño que su HashMap funcione lento, tiene un rendimiento lineal.

+0

Nop, probablemente leyó mal la implementación, está bien. –

+0

@Dimitris Andreou: Lo leí correctamente. ** siempre ** devuelve 31 porque '(X^(X >>> 32))' siempre devuelve '0' para las entradas. – Roman

+0

Obviamente es un largo. (Tenga en cuenta el lanzamiento explícito a int). –

1

JVM arroja 'memoria insuficiente' NO está basado en la memoria disponible. Se lanza cuando el tiempo que se gasta en la recolección de basura es demasiado. check this. Los detalles exactos de implementación varían en función de la JVM y la implementación del recolector de elementos no utilizados.

Aumentar la memoria no ayudaría en este caso. Puede que tenga que elegir otro enfoque.

+0

Quizás tengas razón. Pero la cuestión es que tengo el espacio libre de tamaño de montón y el espacio usado que se muestra en la parte inferior derecha del eclipse para poder ver cuándo se está ejecutando el recolector de elementos no utilizados como cuando se libera gran cantidad de recursos de memoria. Además, la memoria utilizada cuando aparece el error es 150M de los 1000M. Hasta donde sé, el recolector de basura no se ejecuta hasta que se necesita más memoria, donde este no es el caso. Gracias por su respuesta – Pitelk

+0

resulta que eclise la memoria virtual no es lo mismo que la memoria virtual que usa mi programa ... Tengo que configurar la memoria virtual de eclipse en 1G pero luego tengo que hacer lo mismo en la configuración de ejecución. Además, el tamaño del montón mostrado se refiere al tamaño del montón del eclipse utilizado y no a mis aplicaciones. Entonces después ... mi programa usa de hecho toda la memoria. – Pitelk

4

No hay suficiente memoria de pila (increméntela a través de -Xmx, por ejemplo -Xmx512m). Cuando la memoria libre es muy baja, el recolector de basura gasta mucho, mucho tiempo, lo que escanea furiosamente el montón de objetos inalcanzables.

Su hashCode() está bien, puntos extra por usar todos bits del cycleId largo.

Editar. Ahora vi que aumentó la memoria y no ayudó.En primer lugar, ¿está seguro de que hizo para aumentar la memoria? Puede verificar esto mediante jconsole, conectarse a su aplicación y ver su tamaño de almacenamiento dinámico.

Para que se verifique una explicación alternativa, ¿hay algún patrón particular en su cycleId que pueda hacer que esta implementación de hashCode() sea mala? Al igual, sus 32 bits de orden superior son en su mayoría similares a los 32 bits de orden baja. (Sí claro).

Pero no. Incluso si ese fuera el caso, verías una degradación gradual del rendimiento, no una caída brusca en un punto específico (y obtienes una operación OutOfMemoryError y frenzy gc). Así que mi mejor estimación todavía es un problema de memoria. No aumentó el tamaño del almacenamiento dinámico como pensaba, o hay algún otro código de captura de memoria en algún momento. (Puede usar una herramienta como VisualVM para crear un perfil de esto, y obtener un volcado de pila en OOME, y ver qué objetos contiene).

Edit2 Hice negrita la parte correcta de lo anterior.

+0

bien ... el método hachcode se genera automáticamente por eclipse ...puntos tan extra para eclipsar :) En cuanto al Xmx lo he engrasado pero estranguladamente tengo el mismo problema. Lo curioso es que incluso si he aumentado la memoria del montón, los programas comienzan a ralentizarse con la misma cantidad de datos que antes ... – Pitelk

+0

Mira mi respuesta actualizada, estábamos escribiendo al mismo tiempo. –

+0

(Vea también el resto de las respuestas que sugieren que no aumentó el montón de su aplicación, sino que se eclipsa). –

0

¿Cómo está inicializando su HashSet? Debe ser consciente de su patrón de crecimiento. Con cada operación add, comprueba si se está acercando a su capacidad. Si alcanza cierto punto (determinado por su "factor de carga"), realiza una operación de redimensionamiento que puede ser costosa. Desde el JavaDoc (de HashMap - la colección que respalda HashSet):

As a general rule, the default load factor (.75) offers a good tradeoff between time and space costs. Higher values decrease the space overhead but increase the lookup cost (reflected in most of the operations of the HashMap class, including get and put). The expected number of entries in the map and its load factor should be taken into account when setting its initial capacity, so as to minimize the number of rehash operations. If the initial capacity is greater than the maximum number of entries divided by the load factor, no rehash operations will ever occur.

+0

la capacidad inicial se establece en 10.000.000 y los programas comienzan a ser lentos en 1.500.000, por lo que este no es el caso. Gracias por tu awser – Pitelk

0

Estoy bastante decepcionado por el número de respuestas que le dicen al OP para aumentar su tamaño de almacenamiento dinámico en su aplicación. Esa no es una solución: es un parche rápido y sucio, que no resolverá ningún problema subyacente.

me encontré con esta presentación muy informativa: http://www.cs.virginia.edu/kim/publicity/pldi09tutorials/memory-efficient-java-tutorial.pdf

Principalmente la página lista los tamaños mínimos de bytes de cada uno cuando empty--

ArrayList: 40 or 48 
LinkedList: 48 
HashMap: 56 or 120 
HashSet: 72 or 136 

Resulta que un HashSet es prácticamente un HashMap, y (en contra de la intuición) ocupa más memoria a pesar de tener solo valores en lugar de pares clave-valor.

Cuestiones relacionadas