Estoy haciendo un proyecto que genera cientos de subprocesos. Todos estos subprocesos están en una condición "inactiva" (están bloqueados en un objeto Monitor). Me he dado cuenta de que si aumente el número de hilos "dormidos", el programa se ralentizará mucho. Lo "gracioso" es que al mirar al Administrador de tareas parece que cuanto mayor es el número de subprocesos, más libre es el procesador. Reduje el problema a la creación de objetos.Desaceleración en la creación de objetos con muchos subprocesos
¿Alguien me puede explicar esto?
He producido una pequeña muestra para probarlo. Es un programa de consola. Crea un hilo para cada procesador y mide su velocidad con una prueba simple (un "nuevo Objeto()"). No, el "nuevo Objeto()" no está jodido (prueba si no confías en mí). El hilo principal muestra la velocidad de cada hilo. Al presionar CTRL-C, el programa engendra 50 hilos "dormidos". La ralentización comienza con solo 50 hilos. Con alrededor de 250, es muy visible en el Administrador de tareas que la CPU no se usa al 100% (en la mía es 82%).
He intentado tres métodos para bloquear el hilo "dormido": Thread.CurrentThread.Suspend() (malo, malo, lo sé :-)), un bloqueo en un objeto ya bloqueado y un Thread.Sleep (Timeout .Infinito). Es lo mismo. Si comento la fila con el nuevo Object(), y la reemplazo con Math.Sqrt (o sin nada), el problema no está presente. La velocidad no cambia con la cantidad de hilos. ¿Alguien más puede verificarlo? ¿Alguien sabe dónde está el cuello de la botella?
Ah ... debe probarlo en modo de lanzamiento SIN iniciarlo desde Visual Studio. Estoy usando XP sp3 en un procesador dual (sin HT). Lo he probado con .NET 3.5 y 4.0 (para probar los diferentes tiempos de ejecución marco)
namespace TestSpeed
{
using System;
using System.Collections.Generic;
using System.Threading;
class Program
{
private const long ticksInSec = 10000000;
private const long ticksInMs = ticksInSec/1000;
private const int threadsTime = 50;
private const int stackSizeBytes = 256 * 1024;
private const int waitTimeMs = 1000;
private static List<int> collects = new List<int>();
private static int[] objsCreated;
static void Main(string[] args)
{
objsCreated = new int[Environment.ProcessorCount];
Monitor.Enter(objsCreated);
for (int i = 0; i < objsCreated.Length; i++)
{
new Thread(Worker).Start(i);
}
int[] oldCount = new int[objsCreated.Length];
DateTime last = DateTime.UtcNow;
Console.Clear();
int numThreads = 0;
Console.WriteLine("Press Ctrl-C to generate {0} sleeping threads, Ctrl-Break to end.", threadsTime);
Console.CancelKeyPress += (sender, e) =>
{
if (e.SpecialKey != ConsoleSpecialKey.ControlC)
{
return;
}
for (int i = 0; i < threadsTime; i++)
{
new Thread(() =>
{
/* The same for all the three "ways" to lock forever a thread */
//Thread.CurrentThread.Suspend();
//Thread.Sleep(Timeout.Infinite);
lock (objsCreated) { }
}, stackSizeBytes).Start();
Interlocked.Increment(ref numThreads);
}
e.Cancel = true;
};
while (true)
{
Thread.Sleep(waitTimeMs);
Console.SetCursorPosition(0, 1);
DateTime now = DateTime.UtcNow;
long ticks = (now - last).Ticks;
Console.WriteLine("Slept for {0}ms", ticks/ticksInMs);
Thread.MemoryBarrier();
for (int i = 0; i < objsCreated.Length; i++)
{
int count = objsCreated[i];
Console.WriteLine("{0} [{1} Threads]: {2}/sec ", i, numThreads, ((long)(count - oldCount[i])) * ticksInSec/ticks);
oldCount[i] = count;
}
Console.WriteLine();
CheckCollects();
last = now;
}
}
private static void Worker(object obj)
{
int ix = (int)obj;
while (true)
{
/* First and second are slowed by threads, third, fourth, fifth and "nothing" aren't*/
new Object();
//if (new Object().Equals(null)) return;
//Math.Sqrt(objsCreated[ix]);
//if (Math.Sqrt(objsCreated[ix]) < 0) return;
//Interlocked.Add(ref objsCreated[ix], 0);
Interlocked.Increment(ref objsCreated[ix]);
}
}
private static void CheckCollects()
{
int newMax = GC.MaxGeneration;
while (newMax > collects.Count)
{
collects.Add(0);
}
for (int i = 0; i < collects.Count; i++)
{
int newCol = GC.CollectionCount(i);
if (newCol != collects[i])
{
collects[i] = newCol;
Console.WriteLine("Collect gen {0}: {1}", i, newCol);
}
}
}
}
}
Si le preocupa el rendimiento, no debería tener muchas más cadenas (de cuenta). Entre (cpucount + 2) y (cpucount * 2) son buenas reglas generales (y en tu sistema, ambas salen a 4). Use colas de operaciones de E/S asincrónicas para mantener ocupados los pocos hilos en lugar de dormir. La única vez que un hilo debe esperar es cuando se disputa un bloqueo. –
Estoy haciendo corutinas "a cámara lenta". El "tiempo de cambio" entre hilos es irrelevante, así que puedo usar hilos (tengo un "interruptor"/segundo, así que aunque pierda algunos ms para hacer el cambio entre el hilo viejo y el hilo nuevo, no tengo algún problema). Siempre hay una cantidad de subprocesos igual al procesador. Pero si los subprocesos ralentizan todo, entonces tengo un problema. No, no puedo usar la biblioteca asíncrona de MS, porque es "falso". "Reescribe" tu programa. Tengo que usar algunas bibliotecas preexistentes. – xanatos
¿Ha considerado utilizar el TPL en lugar de crear hilos explícitamente? De esta forma, el marco puede decidir la cantidad más apropiada de hilos nativos para hacer el trabajo. –