c# - make - ¿Cómo genero hilos en diferentes núcleos de CPU?
thread invoke c# (10)
Digamos que tenía un programa en C # que hacía algo computacionalmente costoso, como codificar una lista de archivos WAV en MP3. Normalmente codificaba los archivos uno a la vez, pero digamos que quería que el programa averiguara cuántos núcleos de CPU tenía y creara un hilo de codificación en cada núcleo. Entonces, cuando ejecuto el programa en una CPU de cuatro núcleos, el programa se da cuenta de que es una CPU de cuatro núcleos, se da cuenta de que hay cuatro núcleos con los que trabajar, y genera cuatro hilos para la codificación, cada uno funcionando por separado. UPC. ¿Cómo haría esto?
¿Y esto sería diferente si los núcleos se extendieran a través de múltiples CPU físicas? Como en, si tuviera una máquina con dos CPU de cuatro núcleos, ¿hay alguna consideración especial o son los ocho núcleos a través de los dos dados considerados iguales en Windows?
A dónde va cada hilo generalmente lo maneja el sistema operativo ... así que genere 4 hilos en un sistema de 4 núcleos y el sistema operativo decidirá en qué núcleos ejecutar cada uno, que generalmente será de 1 hilo en cada núcleo.
El trabajo del sistema operativo consiste en dividir subprocesos en diferentes núcleos, y lo hará de forma automática cuando sus subprocesos utilicen mucho tiempo de CPU. No te preocupes por eso En cuanto a averiguar cuántos núcleos tiene su usuario, intente Environment.ProcessorCount
en C #.
No deberías tener que preocuparte de hacer esto tú mismo. Tengo aplicaciones .NET multiproceso que se ejecutan en máquinas de doble cuadrante, y no importa cómo se inicien los hilos, ya sea a través de ThreadPool o manualmente, veo una buena distribución de trabajo uniforme en todos los núcleos.
No te molestes en hacer eso.
En su lugar, use el grupo de subprocesos . El grupo de subprocesos es un mecanismo (en realidad, una clase) del marco que puede consultar para un nuevo subproceso.
Cuando solicite un nuevo hilo, le dará un nuevo o en cola el trabajo hasta que se libere un hilo. De esta forma, el marco se encarga de decidir si debe crear más hilos o no, según la cantidad de CPU actuales.
Editar: Además, como ya se ha mencionado, el sistema operativo se encarga de distribuir los hilos entre las diferentes CPU.
Una de las razones por las que no debería (como se ha dicho) tratar de asignar este tipo de cosas usted mismo, es que simplemente no tiene suficiente información para hacerlo correctamente, especialmente en el futuro con NUMA, etc.
Si tiene un hilo de lectura-ejecución, y hay un núcleo inactivo, el kernel ejecutará su hilo, no se preocupe.
En el caso de los hilos gestionados, la complejidad de hacer esto es un grado mayor que el de los hilos nativos. Esto se debe a que los hilos CLR no están directamente relacionados con un hilo del sistema operativo nativo. En otras palabras, el CLR puede cambiar un subproceso administrado desde el subproceso nativo al subproceso nativo como lo considere oportuno. La función Thread.BeginThreadAffinity se proporciona para colocar un hilo gestionado en el paso de bloqueo con un hilo del sistema operativo nativo. En ese punto, podría experimentar con el uso de API nativas para dar afinidad al procesador de subprocesos nativo subyacente. Como todos sugieren aquí, esta no es una muy buena idea. De hecho, hay documentación que sugiere que los hilos pueden recibir menos tiempo de procesamiento si están restringidos a un solo procesador o núcleo.
También puede explorar la clase System.Diagnostics.Process . Allí puede encontrar una función para enumerar los hilos de un proceso como una colección de objetos ProcessThread . Esta clase tiene métodos para establecer ProcessorAffinity o incluso establecer un procesador preferido , sin estar seguro de qué es eso.
Descargo de responsabilidad: He experimentado un problema similar en el que pensé que las CPU estaban infrautilizadas e investigaron muchas de estas cosas; sin embargo, en base a todo lo que leí, parecía que no era una muy buena idea, como lo demuestran los comentarios publicados aquí también. Sin embargo, sigue siendo interesante y una experiencia de aprendizaje para experimentar.
No es necesariamente tan simple como usar el grupo de subprocesos.
De forma predeterminada, el grupo de subprocesos asigna múltiples subprocesos para cada CPU. Como cada hilo involucrado en el trabajo que está realizando tiene un costo (sobrecarga de conmutación de tareas, uso de la limitada caché L1, L2 y tal vez L3 de la CPU, etc.), la cantidad óptima de hilos a utilizar es <= la cantidad de CPU disponibles, a menos que cada hebra solicite servicios de otras máquinas, como un servicio web altamente escalable. En algunos casos, especialmente aquellos que implican más lectura y escritura en el disco duro que la actividad de la CPU, en realidad puede estar mejor con 1 hilo que con múltiples hilos.
Para la mayoría de las aplicaciones, y ciertamente para la codificación WAV y MP3, debe limitar el número de subprocesos de trabajo a la cantidad de CPU disponibles. Aquí hay un código C # para encontrar la cantidad de CPU:
int processors = 1;
string processorsStr = System.Environment.GetEnvironmentVariable("NUMBER_OF_PROCESSORS");
if (processorsStr != null)
processors = int.Parse(processorsStr);
Desafortunadamente, no es tan simple como limitarse a la cantidad de CPU. También debe tener en cuenta el rendimiento del (los) controlador (es) y disco (s) del disco duro.
La única forma en que realmente puede encontrar la cantidad óptima de hilos es probar un error. Esto es particularmente cierto cuando usa discos duros, servicios web y tal. Con los discos duros, es mejor que no uses los cuatro procesadores en tu CPU de cuatro procesadores. Por otro lado, con algunos servicios web, es mejor que haga 10 o incluso 100 solicitudes por CPU.
no puede hacer esto, ya que solo el sistema operativo tiene los privilegios para hacerlo. Si lo decide ... entonces será difícil codificar las aplicaciones. Porque entonces también debe cuidar la comunicación entre procesadores. secciones críticas para cada aplicación tienes que crear tus propios semáforos o mutex ... a qué sistema operativo da una solución común haciéndolo él mismo .......
Definitivamente puede hacer esto escribiendo la rutina dentro de su programa.
Sin embargo, no debe intentar hacerlo, ya que el sistema operativo es el mejor candidato para administrar estas cosas. Me refiero a que el programa de modo de usuario no debería intentar hacerlo.
Sin embargo, a veces, se puede hacer (para usuarios realmente avanzados) para lograr el balanceo de carga e incluso para descubrir un verdadero problema multinúcleo de múltiples hilos (coreo de datos / coherencia del caché ...) ya que diferentes hilos se ejecutarían realmente en diferentes procesadores .
Una vez dicho esto, si aún quieres lograrlo, podemos hacerlo de la siguiente manera. Le proporciono el pseudo código para (SO Windows), sin embargo, también podría hacerlo fácilmente en Linux.
#define MAX_CORE 256
processor_mask[MAX_CORE] = {0};
core_number = 0;
Call GetLogicalProcessorInformation();
// From Here we calculate the core_number and also we populate the process_mask[] array
// which would be used later on to set to run different threads on different CORES.
for(j = 0; j < THREAD_POOL_SIZE; j++)
Call SetThreadAffinityMask(hThread[j],processor_mask[j]);
//hThread is the array of handles of thread.
//Now if your number of threads are higher than the actual number of cores,
// you can use reset the counters(j) once you reach to the "core_number".
Después de llamar a la rutina anterior, los subprocesos siempre se ejecutarán de la siguiente manera:
Thread1-> Core1
Thread2-> Core2
Thread3-> Core3
Thread4-> Core4
Thread5-> Core5
Thread6-> Core6
Thread7-> Core7
Thread8-> Core8
Thread9-> Core1
Thread10-> Core2
...............
Para obtener más información, consulte el manual / MSDN para obtener más información sobre estos conceptos.
Aunque estoy de acuerdo con la mayoría de las respuestas aquí, creo que vale la pena agregar una nueva consideración: tecnología Speedstep.
Cuando ejecuté un trabajo intensivo de una sola CPU en un sistema multi-core, en mi caso un Xeon E5-2430 con 6 núcleos reales (12 con HT) en el servidor de Windows 2012, el trabajo se extendió entre los 12 núcleos, usando alrededor del 8.33% de cada núcleo y nunca dispara un aumento de velocidad. La CPU se mantuvo a 1,2 GHz.
Cuando establecí la afinidad del hilo en un núcleo específico, utilizó ~ 100% de ese núcleo, lo que provocó que la CPU alcanzara un máximo de 2.5 GHz, más del doble del rendimiento.
Este es el programa que utilicé, que simplemente gira aumentando una variable. Cuando se lo llame con -a, establecerá la afinidad en el núcleo 1. La parte de afinidad se basó en esta publicación .
using System;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
namespace Esquenta
{
class Program
{
private static int numThreads = 1;
static bool affinity = false;
static void Main(string[] args)
{
if (args.Contains("-a"))
{
affinity = true;
}
if (args.Length < 1 || !int.TryParse(args[0], out numThreads))
{
numThreads = 1;
}
Console.WriteLine("numThreads:" + numThreads);
for (int j = 0; j < numThreads; j++)
{
var param = new ParameterizedThreadStart(EsquentaP);
var thread = new Thread(param);
thread.Start(j);
}
}
static void EsquentaP(object numero_obj)
{
int i = 0;
DateTime ultimo = DateTime.Now;
if(affinity)
{
Thread.BeginThreadAffinity();
CurrentThread.ProcessorAffinity = new IntPtr(1);
}
try
{
while (true)
{
i++;
if (i == int.MaxValue)
{
i = 0;
var lps = int.MaxValue / (DateTime.Now - ultimo).TotalSeconds / 1000000;
Console.WriteLine("Thread " + numero_obj + " " + lps.ToString("0.000") + " M loops/s");
ultimo = DateTime.Now;
}
}
}
finally
{
Thread.EndThreadAffinity();
}
}
[DllImport("kernel32.dll")]
public static extern int GetCurrentThreadId();
[DllImport("kernel32.dll")]
public static extern int GetCurrentProcessorNumber();
private static ProcessThread CurrentThread
{
get
{
int id = GetCurrentThreadId();
return Process.GetCurrentProcess().Threads.Cast<ProcessThread>().Single(x => x.Id == id);
}
}
}
}
Y los resultados:
Velocidad del procesador, como lo muestra el Administrador de tareas, similar a lo que informa CPU-Z: