2012-04-15 12 views
11

Estoy usando map<MyStruct, I*> map1;. Aparentemente, el 9% de mi tiempo total de aplicación se gasta allí. Específicamente en una línea de una de mis funciones principales. El mapa no es muy grande (< 1k casi siempre, < 20 es común).stl map performance?

¿Existe una implementación alternativa que pueda querer usar? Creo que no debería escribir el mío, pero podría si pensase que era una buena idea.

Información adicional: Siempre compruebo antes de agregar un elemento. Si existe una clave, necesito informar un problema. Después de un punto, utilizaré el mapa en gran medida para las búsquedas y no añadiré más elementos.

+7

Sin el código fuente, no podemos decirlo. También mire la versión de 'insert' que devuelve un par (esto responderá a su segunda pregunta) –

+1

¿Podría compartir información sobre su función de comparación en' MyStruct' que también utiliza el mapa? – amit

+0

¿Puede proporcionarnos un poco más de información sobre lo que está haciendo dentro de la función mencionada? Como la complejidad de búsqueda del mapa es O (log n), no estoy seguro de dónde vendrá una mejora. –

Respuesta

6

¿Sería más rápido simplemente hacer un insert y verificar si el pair.second es false si la clave ya existe?

como esto

if (myMap.insert(make_pair(MyStruct, I*)).second == false) 
{ 
    //report error 
} 
else 
    // inserted new value 

en lugar de hacer una llamada find cada vez?

+0

Excelente idea. Aunque agregar memebers no es la línea con el problema de rendimiento.Añadiré eso y echaré un vistazo al rendimiento –

+0

Si difieres la búsqueda de si realmente tienes un choque, probablemente elimines una carga de búsquedas que no son necesarias a menos que estés haciendo algo bastante extraño y generando más valores duplicados que únicos. El método 'insert' que he detallado devuelve un par con' first' como iterador del nuevo valor o duplicado, el segundo es booleano con 'true' que significa éxito y' false' que significa entrada duplicada. – EdChum

+0

Me siento un poco tonto al no hacer inserciones de esta manera. Debo haber tenido prisa o pensé que era un código desechable. –

2

La forma en que usa el mapa, está realizando búsquedas sobre la base de una instancia MyStruct y, dependiendo de su implementación particular, la comparación requerida puede o no ser costosa.

+0

hmm, no creo que lo necesite en un orden específico. Me doy cuenta de que la mayoría del código estaba comprobando si las dos variables son nulas y creo que alguna vez lo es. Solo quitar ese cheque lo hizo lo suficientemente rápido como para desaparecer del generador de perfiles (estoy sorprendido). Si vuelve a aparecer, jugaré más con la función de comparación. +1 y posiblemente acepte. –

4

Puede ser una posibilidad remota, pero para colecciones pequeñas, a veces el factor más crítico es el rendimiento cache.

Desde std::map implementa un árbol Rojo-Negro, que es [yo sepa] no es muy caché eficientes - tal vez aplicar la hoja como std::vector<pair<MyStruct,I*>> sería una buena idea, y utilizar la búsqueda binaria allí [en lugar de un mapa look-ups] , como mínimo, debería ser eficiente una vez que empiece a buscar [detener insertando elementos], ya que std::vector es más probable que quepa en el caché que map.

Este factor [cpu-cache] generalmente se descuida y se oculta como constante en la notación O grande, pero para colecciones grandes puede tener un efecto importante.

+0

Probablemente tenga una clase que use dos vectores internamente en lugar de un par y que se vea similar a un mapa. –

+1

@ acidzombie24: El 'par' es solo una sugerencia. Con respecto a dos vectores: estoy de acuerdo, probablemente sea incluso mejor que un 'vector' de' pair's [menos campos -> menos uso de memoria -> más probable que todo el mapa encaje en el caché]. La respuesta apuntaba solo a enfatizar la importancia de la memoria caché en colecciones pequeñas, y recordarles que es mucho más importante que una gran notación O para estas, ya que las constantes no deben descuidarse. – amit

+1

Hemos visto grandes mejoras en nuestro código en ciertos casos cuando cambiamos de mapa a vector. Sospechamos que el rendimiento del almacenamiento en caché es mucho mayor con el vector. –

1

¿Existe una implementación alternativa que pueda querer usar? Creo que no debería escribir el mío, pero podría si pensase que era una buena idea.

Si comprende bien el problema, debe detallar cómo su implementación será superior.

¿Es map la estructura correcta? Si es así, entonces la implementación de su biblioteca estándar probablemente será de buena calidad (bien optimizada).

Can MyStruct comparación se puede simplificar?

¿Dónde está el problema - cambio de tamaño? ¿buscar?

¿Ha minimizado la copia y asignado los costos para sus estructuras?

+0

Problema: búsqueda. Estructura adecuada: tal vez. Necesito encontrar una estructura por clave (que se debe comparar con otra estructura) y el objeto de interfaz asociado con ella. No creo que el orden sea un problema, ya que generalmente es muy pequeño. Copiar/Asignar: Bueno, asigno copiando un ptr. Es inmutable, así que no tengo que preocuparme de que se elimine. –

+0

Estaba comprobando si ambas estructuras son nulas en mi función cmp. Quitarlo lo hizo lo suficientemente rápido como para no aparecer más. +1 para la respuesta global. –

5

En lugar de map puede probar unordered_map que utiliza las teclas de acceso directo, en lugar de un árbol, para buscar elementos. This answer da algunas pistas sobre cuándo preferir unordered_map sobre map.

+3

Para colecciones pequeñas, los mapas hash son * normalmente * más lentos que los árboles rojo-negro, por lo que espero que su uso en este caso solo tenga un efecto negativo. – amit

+0

@amit: incluso para una pequeña colección con 20 elementos, [esta comparación simple] (http://ideone.com/YupK4) - bajo la restricción de un tipo de clave simple - me da un mejor rendimiento para el 'unordered_set '. Una comparación real incluiría 'MyStruct' y una función hash para ello. –

+0

Miré unordered_map. Después de leer el mensaje de error (es grande ...) me doy cuenta de que necesito implementar una función de hash. No tengo idea de cómo hacer eso. ¿Cómo hago hash a char *? Uno que puede tener de 1 a 20 chars de largo (o más) –

35

Primero debe comprender qué es un mapa y qué representan las operaciones que está realizando. Un std::map es un árbol binario equilibrado, la búsqueda tomará operaciones O(log N), cada una de las cuales es una comparación de las teclas más algunas adicionales que puede ignorar en la mayoría de los casos (administración de punteros). La inserción lleva más o menos el mismo tiempo para ubicar el punto de inserción, más la asignación del nuevo nodo, la inserción real en el árbol y el reequilibrio. La complejidad es nuevamente O(log N) aunque las constantes ocultas son más altas.

Cuando intenta determinar si una clave está en el mapa antes de la inserción, está incurriendo en el costo de la búsqueda y si no tiene éxito, el mismo costo para ubicar el punto de inserción. Puede evitar el costo adicional usando std::map::insert que devuelve un par con un iterador y un bool que le informa si la inserción realmente sucedió o si el elemento ya estaba allí.

Más allá de eso, es necesario entender lo costoso que es comparar sus llaves, que se cae de lo que muestra el interrogación (MyStruct pudo contener sólo una int o un millar de ellos), que es algo que hay que tener en cuenta.

Por último, podría darse el caso de que un map no es la estructura de datos más eficiente para sus necesidades, y es posible que desee considerar el uso ya sea un (tabla hash) std::unordered_map que ha esperado inserciones de tiempo constantes (si la función hash no es horrible) o para pequeños conjuntos de datos incluso un conjunto ordenado simple (o std::vector) en el que puede utilizar la búsqueda binaria para localizar los elementos (esto reducirá el número de asignaciones, al costo de inserciones más costosas, pero los tipos retenidos son lo suficientemente pequeños como para valer la pena)

Como siempre con el rendimiento, mida y luego intente comprender dónde se está gastando el tiempo. También tenga en cuenta que un 10% del tiempo dedicado a una función o estructura de datos en particular puede ser mucho o casi nada, dependiendo de cuál sea su aplicación. Por ejemplo, si su aplicación solo realiza búsquedas e inserciones en un conjunto de datos, y eso solo requiere un 10% de la CPU, ¡tiene mucho que optimizar en cualquier otro lugar!

+0

Excelente respuesta. Una sugerencia no fue ordenada (hash) puede ser mala ya que el tamaño de la tabla es pequeño. Definitivamente usaré insertar en mi otra ubicación. Usted golpea en todos los puntos importantes –

+2

Como alternativa a un inserto ciego, puede usar 'lower_bound', verificar la clave, y luego usar inserción insinuada (si desea evitar una construcción de objeto potencialmente costosa). –

+0

La estructura es en realidad una plantilla de envoltorio rápido que escribí que contiene T *. El punto es tan ops como '<' y '==' hará '* ptr OP * other' por lo que no se compara por la dirección de ptr. Me hace la vida inmensamente más fácil cuando uso contenedores. Esa es otra buena pista. +1 @KerrekSB –

1

Como se indica en los comentarios, sin el código adecuado, hay pocas respuestas universales para darle. Sin embargo, si MyStruct es realmente grande, la copia de la pila puede ser costosa. Tal vez tenga sentido para almacenar punteros a MyStruct e implementar su propio mecanismo de comparación:

template <typename T> struct deref_cmp { 
    bool operator()(std::shared_ptr<T> lhs, std::shared_ptr<T> rhs) const { 
    return *lhs < *rhs; 
    } 
}; 

std::map<std::shared_ptr<MyStruct>, I*, deref_cmp<MyStruct>> mymap; 

Sin embargo, esto es algo que tendrá al perfil. Es podría acelerar las cosas.

que se vería a un elemento como este

template <typename T> struct NullDeleter { 
    void operator()(T const*) const {} 
}; 
// needle being a MyStruct 
mymap.find(std::shared_ptr<MyStruct>(&needle,NullDeleter())); 

hace falta decir que hay más posibilidades de optimizar.

+0

MyStruct es en realidad T * donde T es una plantilla;). Vea mi primer comentario a Johann Gerell, respuesta –

+1

Bueno, en ese caso no tiene que molestarse con esta redirección. – bitmask

+0

sí. así que +1 para la sugerencia de todos modos :) –