Tengo dos diccionarios con diferentes tipos de valores: Dictionary<int, string[]> y Dictionary<int, int[]> . Supongamos que generamos matrices aleatorias en un bucle y las insertamos en los diccionarios (en C#).
var d1 = new Dictionary<int, string[]>(); var d2 = new Dictionary<int, int[]>(); var sw = Stopwatch.StartNew(); for (int i = 0; i < 40000000; i++) { string[] sarr = new string[10]; for (int j = 0; j < 10; j++) { sarr[j] = j.ToString(); } int[] iarr = new int[10]; for (int j = 0; j < 10; j++) { iarr[j] = j; } d1[i] = sarr; // (1) d2[i] = iarr; // (2) } sw.Stop();Tenga en cuenta las dos últimas líneas del bucle for. Cuando ejecuto el código anterior, toma alrededor de 13,9 segundos en mi máquina. Ahora, cuando comento solo (1), tarda unos 13,7 segundos. Y si solo comento (2), entonces toma alrededor de 20 segundos. En otras palabras, al eliminar (2) ¡se vuelve mucho más lento! Repetí esto varias veces y puedo confirmar que el comportamiento es consistente.
¿Alguien puede explicar cómo esto es posible?
Hice este experimento porque noté que insertar string[] es más lento que insertar int[] aunque estoy usando la misma clave en los dos diccionarios. Me gustaría saber por qué insertar string [] también puede ser más lento que insertar int[] .
Entonces, mi pregunta es doble: (1) ¿cómo es que eliminar una línea del código anterior puede hacer que las cosas sean más lentas, (2) por qué insertar string[] es más lento que insertar int[] ?
FYI, estoy usando la última versión de .NET 5 (5.0.103). Probé el código tanto en Windows como en Linux, y el comportamiento fue el mismo. Constantemente veo el mismo problema usando el modo de depuración o de lanzamiento.
Cuando diferencio los IL de la versión comentada frente a la original, la versión comentada no tiene una llamada a la función set_Item del diccionario como se esperaba. Las demás cosas son más o menos iguales.
IL_0079: ldloc.1 // dictionary2 IL_007a: ldloc.3 // key IL_007b: ldloc.s numArray IL_007d: callvirt instance void class [System.Collections]System.Collections.Generic.Dictionary`2<int32, int32[]>::set_Item(!0/*int32*/, !1/*int32[]*/) IL_0082: nopLa parte anterior se eliminó cuando comenté (2), por ejemplo.
Para ayudar a reproducir este problema, creé un repositorio simple con Benchmark.NET: https://github.com/sangkilc/TestDictionary . En este repositorio, reduje la cantidad de iteraciones (de 40 millones a 4 millones) porque lleva demasiado tiempo.
En mi máquina los resultados son:
.NET Core SDK=5.0.103 [Host] : .NET Core 5.0.3 (CoreCLR 5.0.321.7212, CoreFX 5.0.321.7212), X64 RyuJIT DefaultJob : .NET Core 5.0.3 (CoreCLR 5.0.321.7212, CoreFX 5.0.321.7212), X64 RyuJIT | Method | Mean | Error | StdDev | |--------- |--------:|---------:|---------:| | TestBoth | 1.269 s | 0.0222 s | 0.0208 s | | TestOne | 1.381 s | 0.0257 s | 0.0241 s | Según la observación de @TheodorZoulias, si modifico d2 a una matriz 2D, la diferencia se vuelve más significativa:
| Method | Mean | Error | StdDev | |--------- |--------:|---------:|---------:| | TestBoth | 1.137 s | 0.0195 s | 0.0163 s | | TestOne | 1.373 s | 0.0246 s | 0.0345 s |Esta no es una respuesta, solo quería mostrar algunas fotos.
Tengo el código compilado en Release para dos de sus escenarios:
Tenga en cuenta que eliminé el código relacionado con Stopwatch , porque solo estamos interesados en Dictionary .
Tengo dotTrace en mi máquina, así que obtuve algunos resultados de perfiles (línea por línea).
Para ambos escenarios:
Para el (1) único escenario:
Como mencionó que el patrón es consistente, ejecuté el perfilado solo una vez.
A partir de los resultados, podríamos decir que las funciones relacionadas con Dictionary no son las principales contribuyentes al llamado "comportamiento de rendimiento extraño", probablemente GC lo sea.