¿Alguien podría explicarme por qué la función Contains() de la lista de genéricos es tan lenta?
Tengo una lista con aproximadamente un millón de números, y el código que constantemente comprueba si hay un número específico dentro de estos números.
He intentado hacer lo mismo usando Dictionary y la función ContainsKey(), y fue aproximadamente 10-20 veces más rápido que con la Lista.
Por supuesto, realmente no quiero usar Dictionary para ese propósito, porque no estaba destinado a ser usado de esa manera.
Entonces, la verdadera pregunta aquí es, ¿hay alguna alternativa a List.Contains(), pero no tan divertida como Dictionary.ContainsKey()?
¡Gracias de antemano!C#, Lista <T> .Contains() - ¿demasiado lento?
Respuesta
Si usted apenas está mirando por la existencia, HashSet<T>
en .NET 3.5 es su mejor opción - diccionario rendimiento similar, pero sin par clave/valor - sólo los valores:
HashSet<int> data = new HashSet<int>();
for (int i = 0; i < 1000000; i++)
{
data.Add(rand.Next(50000000));
}
bool contains = data.Contains(1234567); // etc
El diccionario no es tan malo, porque las claves de un diccionario están diseñadas para ser encontradas rápidamente. Para encontrar un número en una lista, necesita repetir en toda la lista.
Por supuesto, el diccionario solo funciona si sus números son únicos y no ordenados.
Creo que también hay una clase HashSet<T>
en .NET 3.5, sino que también permite solo elementos únicos.
Un diccionario
List.Contains es una operación O (n).
Dictionary.ContainsKey es una operación O (1), ya que utiliza el código hash de los objetos como una clave, lo que le da una capacidad de búsqueda más rápida.
No creo que sea una buena idea tener una lista que contenga un millón de entradas. No creo que la clase List haya sido diseñada para ese propósito. :)
¿No es posible guardar esas entidades millon en un RDBMS, por ejemplo, y realizar consultas en esa base de datos?
Si no es posible, entonces usaría un diccionario de todos modos.
No creo que haya nada inapropiado acerca de una lista con un millón de elementos, sino que probablemente no desee seguir realizando búsquedas lineales en ella. –
Terminé utilizando HashSet
De acuerdo, no hay nada de malo en una lista ni en una matriz con tantas entradas. Simplemente no escanee por valores. –
Un SortedList será más rápido para buscar (pero más lento para insertar elementos)
¿Por qué es un diccionario inapropiado?
Para ver si un valor particular está en la lista, necesita recorrer toda la lista. Con un diccionario (u otro contenedor basado en hash) es mucho más rápido reducir el número de objetos que necesita comparar. La clave (en su caso, el número) es hash y eso le da al diccionario el subconjunto fraccionario de objetos para comparar.
estoy usando este en el Marco Compacto donde no hay soporte para HashSet, he optado por un Diccionario donde ambas cadenas son el valor que estoy buscando.
Significa que obtengo la lista <> funcionalidad con rendimiento del diccionario. Es un poco hacky, pero funciona.
Si está utilizando un diccionario en lugar de un HashSet, también podría establecer el valor en "" en lugar de la misma cadena que la clave. De esa forma, usará menos memoria. Alternativamente, incluso podría usar Dictionary
En el diccionario, una referencia 'string' y un valor' bool' hacen una diferencia de 3 o 7 bytes, para sistemas de 32 o 64 bits, respectivamente. Sin embargo, tenga en cuenta que el tamaño de cada entrada se redondea a múltiplos de 4 u 8, respectivamente. La elección entre 'string' y' bool' no puede hacer ninguna diferencia en el tamaño. La cadena vacía '" "' siempre existe en la memoria como la propiedad estática 'string.Empty', por lo que no importa si la usa en el diccionario o no. (Y se utiliza en otros lugares de todos modos.) – Wormbo
Esto no es exactamente una respuesta a su pregunta, pero tengo una clase que aumenta el rendimiento de Contiene() en una colección. Subclasé una cola y agregué un diccionario que mapea códigos hash a listas de objetos.La función Dictionary.Contains()
es O (1) mientras que List.Contains()
, Queue.Contains()
y Stack.Contains()
son O (n).
El tipo de valor del diccionario es una cola que contiene objetos con el mismo código hash. La persona que llama puede proporcionar un objeto de clase personalizado que implemente IEqualityComparer. Puede usar este patrón para Stacks o Lists. El código solo necesitaría algunos cambios.
/// <summary>
/// This is a class that mimics a queue, except the Contains() operation is O(1) rather than O(n) thanks to an internal dictionary.
/// The dictionary remembers the hashcodes of the items that have been enqueued and dequeued.
/// Hashcode collisions are stored in a queue to maintain FIFO order.
/// </summary>
/// <typeparam name="T"></typeparam>
private class HashQueue<T> : Queue<T>
{
private readonly IEqualityComparer<T> _comp;
public readonly Dictionary<int, Queue<T>> _hashes; //_hashes.Count doesn't always equal base.Count (due to collisions)
public HashQueue(IEqualityComparer<T> comp = null) : base()
{
this._comp = comp;
this._hashes = new Dictionary<int, Queue<T>>();
}
public HashQueue(int capacity, IEqualityComparer<T> comp = null) : base(capacity)
{
this._comp = comp;
this._hashes = new Dictionary<int, Queue<T>>(capacity);
}
public HashQueue(IEnumerable<T> collection, IEqualityComparer<T> comp = null) : base(collection)
{
this._comp = comp;
this._hashes = new Dictionary<int, Queue<T>>(base.Count);
foreach (var item in collection)
{
this.EnqueueDictionary(item);
}
}
public new void Enqueue(T item)
{
base.Enqueue(item); //add to queue
this.EnqueueDictionary(item);
}
private void EnqueueDictionary(T item)
{
int hash = this._comp == null ? item.GetHashCode() : this._comp.GetHashCode(item);
Queue<T> temp;
if (!this._hashes.TryGetValue(hash, out temp))
{
temp = new Queue<T>();
this._hashes.Add(hash, temp);
}
temp.Enqueue(item);
}
public new T Dequeue()
{
T result = base.Dequeue(); //remove from queue
int hash = this._comp == null ? result.GetHashCode() : this._comp.GetHashCode(result);
Queue<T> temp;
if (this._hashes.TryGetValue(hash, out temp))
{
temp.Dequeue();
if (temp.Count == 0)
this._hashes.Remove(hash);
}
return result;
}
public new bool Contains(T item)
{ //This is O(1), whereas Queue.Contains is (n)
int hash = this._comp == null ? item.GetHashCode() : this._comp.GetHashCode(item);
return this._hashes.ContainsKey(hash);
}
public new void Clear()
{
foreach (var item in this._hashes.Values)
item.Clear(); //clear collision lists
this._hashes.Clear(); //clear dictionary
base.Clear(); //clear queue
}
}
Mi prueba simple muestra que mis HashQueue.Contains()
corre mucho más rápido que Queue.Contains()
. Ejecutar el código de prueba con el recuento establecido en 10,000 requiere 0.00045 segundos para la versión de HashQueue y 0.37 segundos para la versión de Queue. Con una cuenta de 100,000, la versión HashQueue toma 0.0031 segundos mientras que la cola demora 36.38 segundos.
Aquí está mi código de prueba:
static void Main(string[] args)
{
int count = 10000;
{ //HashQueue
var q = new HashQueue<int>(count);
for (int i = 0; i < count; i++) //load queue (not timed)
q.Enqueue(i);
System.Diagnostics.Stopwatch sw = System.Diagnostics.Stopwatch.StartNew();
for (int i = 0; i < count; i++)
{
bool contains = q.Contains(i);
}
sw.Stop();
Console.WriteLine(string.Format("HashQueue, {0}", sw.Elapsed));
}
{ //Queue
var q = new Queue<int>(count);
for (int i = 0; i < count; i++) //load queue (not timed)
q.Enqueue(i);
System.Diagnostics.Stopwatch sw = System.Diagnostics.Stopwatch.StartNew();
for (int i = 0; i < count; i++)
{
bool contains = q.Contains(i);
}
sw.Stop();
Console.WriteLine(string.Format("Queue, {0}", sw.Elapsed));
}
Console.ReadLine();
}
simplemente añadí caso de prueba para la tercera HashSet
creo que tengo la respuesta! Sí, es cierto que Contiene() en una lista (matriz) es O (n), pero si la matriz es corta y está utilizando tipos de valores, todavía debe ser bastante rápido. Pero al usar el CLR Profiler [descarga gratuita de Microsoft], descubrí que Contiene() es un valor de boxeo para compararlos, lo que requiere una asignación de montón, que es MUY cara (lenta). [Nota: Esto es .Net 2.0; otras versiones de .Net no probadas.]
Aquí está la historia completa y la solución. Tenemos una enumeración llamada "VI" e hicimos una clase llamada "ValueIdList" que es un tipo abstracto para una lista (matriz) de objetos VI. La implementación original estaba en los antiguos .Net 1.1 días, y usaba ArrayList encapsulado. Recientemente descubrimos en http://blogs.msdn.com/b/joshwil/archive/2004/04/13/112598.aspx que una lista genérica (Lista < VI>) funciona mucho mejor que ArrayList en tipos de valores (como nuestra enumeración VI) porque los valores no tienen que estar encuadrados. Es verdad y funcionó ... casi.
El CLR Profiler reveló una sorpresa. He aquí una parte de la gráfica de la asignación:
- ValueIdList :: Contiene bool (VI) 5.5MB (34,81%)
- Generic.List :: Contiene bool (< DESCONOCIDO>) 5.5MB (34,81%)
- Generic.ObjectEqualityComparer < T> :: bool Equals (< DESCONOCIDO> < DESCONOCIDO>) 5.5MB (34,88%)
- Values.VI 7.7MB (49,03%)
Como se puede ver, contiene() Sorpresa individualmente llama a Generic.ObjectEqualityComparer.Equals(), que aparentemente requiere el boxeo de un valor VI, que requiere una costosa asignación de montón. Es extraño que Microsoft elimine el boxeo en la lista, solo para volver a necesitarlo para una operación simple como esta.
Nuestra solución fue volver a escribir la implementación de Contiene(), que en nuestro caso era fácil de hacer porque ya estábamos encapsulando el objeto de lista genérico (_temas). Aquí está el código simple:
public bool Contains(VI id)
{
return IndexOf(id) >= 0;
}
public int IndexOf(VI id)
{
int i, count;
count = _items.Count;
for (i = 0; i < count; i++)
if (_items[i] == id)
return i;
return -1;
}
public bool Remove(VI id)
{
int i;
i = IndexOf(id);
if (i < 0)
return false;
_items.RemoveAt(i);
return true;
}
La comparación de los valores de VI se está haciendo ahora en nuestra propia versión de IndexOf(), que no requiere el boxeo, y es muy rápido. Nuestro programa particular se aceleró un 20% después de esta simple reescritura. O (n) ... no hay problema! ¡Solo evita el uso de memoria desperdiciado!
Gracias por el consejo, me atrapó el mal desempeño del boxeo. Una implementación personalizada de 'Contiene' es mucho más rápida para mi caso de uso. –
- 1. f # compilando demasiado lento
- 2. DivideByZeroException demasiado lento
- 3. IndexOf demasiado lento en la lista. Una solución más rápida?
- 4. Anular .NET Lista genérica <MyType> .Contains (MyTypeInstance)?
- 5. ANDROID - procesamiento dx demasiado lento
- 6. Postgresql Spatial Query demasiado lento
- 7. PHP: scandir() es demasiado lento
- 8. glReadPixels demasiado lento para usar
- 9. Python, multihilo demasiado lento, multiproceso
- 10. ¿Por qué es la lista <T> .IndexOf() mucho más rápida que la lista <T> .Contains()?
- 11. C++ cadena de flujo es demasiado lento, ¿cómo acelerar?
- 12. C color de fondo # WinForms DataGridView haciendo demasiado lento
- 13. Consultas SQL: ¿qué tan lento es demasiado lento?
- 14. Dispositivo virtual Android super lento - ¿PC demasiado lento?
- 15. jQuery: $ (document) .ready() demasiado lento en IE
- 16. Formato de cadena de Python demasiado lento
- 17. Código Postgresql libpq simple demasiado lento?
- 18. Usar git sobre sshfs es demasiado lento
- 19. Forma más rápida de hacer una lista <T> .Contains()
- 20. ¿Cómo se puede encontrar la lista <T> .Contains() Find Matching Items?
- 21. Urllib2 & BeautifulSoup: Buena pareja pero demasiado lento - urllib3 & threads?
- 22. ICollection <T> .Contains sobre tipos personalizados
- 23. STS Spring con Roo and Maven: ¿demasiado lento?
- 24. El fadein 'lento' de Jquery es demasiado rápido
- 25. Drupal 7 es demasiado lento en "primera carga"
- 26. Docx to pdf usando openoffice sin cabeza demasiado lento
- 27. consulta MySQL con combinación izquierda es demasiado lento
- 28. ¡Optimizando mi declaración de mysql! - RAND() DEMASIADO LENTO
- 29. Scrap 's Scrapyd demasiado lento con las arañas de programación
- 30. scala-io, moviendo el archivo desde tmpfs demasiado lento
¿Qué problema hay en el diccionario? Está destinado para su uso en caso como el suyo. – Kamarey
@Kamarey: HashSet puede ser una mejor opción. –
HashSet es lo que estaba buscando. – DSent