2008-12-18 20 views
9

Tengo un Dictionary<string,int> que tiene el potencial de contener más de 10+ millones de claves únicas. Estoy tratando de reducir la cantidad de memoria que esto requiere, manteniendo la funcionalidad del diccionario.C# Dictionary Memory Management

Tuve la idea de almacenar un hash de la cadena como un largo, esto disminuye el uso de memoria de las aplicaciones a una cantidad aceptable (~ 1.5 gigas a ~ .5 gigas), pero no me siento muy bien acerca de mi método para hacer esto.

long longKey= 
BitConverter.ToInt64(cryptoTransformSHA1.ComputeHash(enc.GetBytes(strKey)), 0); 

Básicamente esto corta la final de un hash SHA1, y pone la primera parte de ella en un largo, que luego utilizo como una clave. Si bien esto funciona, al menos para los datos con los que estoy probando, no creo que esta sea una solución muy confiable debido a la mayor posibilidad de colisiones clave.

¿Hay alguna otra forma de reducir la huella de memoria del diccionario, o el método que he descrito anteriormente no es tan horrible como creo que es?

[edit] Para aclarar, necesito mantener la capacidad de buscar un valor contenido en el Diccionario usando una cadena. Almacenar la cadena real en el diccionario lleva a mucha memoria. Lo que me gustaría hacer en su lugar es usar un Dictionary<long,int> donde el largo es el resultado de una función de hashing en la cadena.

+1

Diccionario ? – Diadistis

+1

Dudo que la posibilidad de colisiones sea realista con un hash de 64 bits. –

+0

Me imagino que ese sea el caso también, pero simplemente 'cortar' los bytes a la mitad parece algo dudoso. –

Respuesta

11

Así que he hecho algo similar recientemente y por una serie de razones que son bastante exclusivas de mi aplicación no usé una base de datos De hecho, intenté dejar de usar una base de datos. Descubrí que GetHashCode se mejoró significativamente en 3.5. Una nota importante, NUNCA ALMACENE PERSISTENTEMENTE LOS RESULTADOS DE GetHashCode. NUNCA JAMÁS. No se garantiza que sean consistentes entre las versiones del marco.

Así que realmente necesita realizar un análisis de sus datos ya que las diferentes funciones hash podrían funcionar mejor o peor en sus datos. También necesita dar cuenta de la velocidad. Como regla general, las funciones hash criptográficas no deberían tener muchas colisiones, incluso si el número de hash se mueve a miles de millones. Para las cosas que necesito para ser único, generalmente utilizo SHA1 Managed. En general, CryptoAPI tiene un rendimiento terrible, incluso si las funciones hash subyacentes funcionan bien.

Para un hash de 64 bits Actualmente uso Lookup3 y FNV1, que son hashes de 32 bits, juntos. Para que ocurra una colisión, ambos tendrían que colisionar, lo que es matemáticamente improbable y no he visto pasar más de 100 millones de hashes. Puede encontrar el código disponible públicamente en la web.

Realice su propio análisis. Lo que funcionó para mí puede no funcionar para usted. En realidad, dentro de mi oficina, diferentes aplicaciones con diferentes requisitos en realidad usan diferentes funciones hash o combinaciones de funciones hash.

Evitaría cualquier función hash no comprobada. Hay tantas funciones hash como personas que piensan que deberían escribirlas. Haga su prueba de investigación y prueba de prueba.

+0

Implementé una versión de su idea de hash de 64 bits, y las pruebas preliminares fueron bien. Voy a realizar algunas pruebas adicionales, pero esta parece ser la mejor solución entre el tamaño de la memoria y el tiempo de acceso para mis propósitos. – blogsdon

+0

Cool. Me gusta la técnica de hash de 64 bits. ¿Qué funciones hash usaste? –

+0

+1 por responder la pregunta y no intentar recomendar una base de datos relacional. –

3

¿Por qué no solo usa GetHashCode() para obtener un hash de la cadena?

+0

GetHashCode() no es confiable en absoluto ... – Diadistis

+0

Lo intenté primero, pero causó colisiones. – blogsdon

+0

No sabía que GetHashCode no era confiable. ¿Más información? –

2

Con las implementaciones de tabla hash con las que he trabajado anteriormente, el hash te lleva a un depósito que a menudo es una lista de enlaces de otros objetos que tienen el mismo hash. Los valores hash no son únicos, pero son lo suficientemente buenos como para dividir los datos en listas muy manejables (a veces solo 2 o 3 de largo) que luego puede buscar para encontrar su elemento real.

La clave para un buen hash no es su exclusividad, sino su velocidad y capacidad de distribución ... quiere que se distribuya lo más uniformemente posible.

+0

El diccionario no funciona de esta manera. No permitirá colisiones clave. Tendría que usar una estructura de datos diferente y para manejar las colisiones que necesitaría para almacenar la clave hash y la clave real, a menos que también sepa el valor que está buscando. Esto no salvaría ninguna memoria. – tvanfosson

+0

Las claves hash pueden ser congruentes, pero no equivalentes. Él está usando una cadena hash COMO la clave. Por eso no puede usar string.GetHashCode() como la clave, debido a los engaños dados el tamaño de la muestra. –

5

Por cierto, las funciones hash/hash criptográficas son excepcionalmente malas para los diccionarios. Son grandes y lentos Al resolver el problema (tamaño) solo ha introducido otro problema más grave: la función no distribuirá la entrada de manera uniforme por más tiempo, destruyendo así la propiedad más importante de un buen hash para abordar el direccionamiento libre de colisiones (como parece que te has dado cuenta)

/EDITAR: Como Andrew ha notado, GetHashCode es la solución para este problema ya que ese es su uso previsto. Y como en un diccionario verdadero, tendrás que evitar las colisiones. Uno de los mejores esquemas para eso es double hashing. Desafortunadamente, la única forma 100% confiable será almacenar los valores originales. De lo contrario, hubieras creado una compresión infinita, que sabemos que no puede existir.

+0

En efecto, eso es lo que está haciendo. En lugar de Dict su Dict y la clave es el criptohash de la cadena original, mientras que antes string.gethashcode estaba causando claves duplicadas en la muestra orignal. –

+0

Nicholas, tienes razón, pero un hash cryto (lisiado) es * todavía * un hash malo, incluso si se usa en hash doble. –

+0

Puede poner ese ceño al revés al encapsular la firma en una clase y simular que la firma en sí es un objeto opaco. Mi ejemplo a continuación hace exactamente eso. Tenga en cuenta que debería usar una base de datos de todos modos ... – user7116

7

Con 10 millones de registros impares, ¿ha considerado utilizar una base de datos con un índice no agrupado? Las bases de datos tienen muchos más trucos bajo la manga para este tipo de cosas.

Hashing, por definición, y bajo cualquier algoritmo, tiene el potencial de colisiones, especialmente con grandes volúmenes. Dependiendo de la situación, sería muy cauteloso con esto.

Usar las cuerdas puede ocupar espacio, pero es confiable ...si está en x64, no es necesario que sea demasiado grande (aunque definitivamente cuenta como "grande" ;-))

2

Simplemente vaya a SQLite. No es probable que le gane, e incluso si lo hace, probablemente no valga la pena el tiempo/esfuerzo/complejidad.

SQLite.