2008-08-28 24 views
50

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?¿Cómo genero hilos en diferentes núcleos de CPU?

¿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?

Respuesta

51

No te molestes en hacer eso.

En su lugar use el Thread Pool. 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á uno nuevo o lo enrutará 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.

+1

¿Qué pasa si no lo has' ¿Tienes .NET? – Crashworks

+52

Esta es una pregunta relacionada con .NET. ¿Por qué no tendrías .NET? –

+0

más a esa inclusión de TPL (Task Parallel) también toma este trabajo usted puede leer más aquí http://msdn.microsoft.com/en-us/magazine/cc163340.aspx –

1

Donde cada hilo va generalmente es manejado por 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.

1

El trabajo del sistema operativo consiste en dividir los subprocesos en diferentes núcleos, y lo hará automáticamente cuando los subprocesos usan 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#.

2

No debería tener que preocuparse por hacerlo usted 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.

1

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 read-to-run, y hay un núcleo inactivo, el kernel va a ejecutar su hilo, no se preocupe.

8

En el caso de subprocesos administrados, la complejidad de hacerlo es un grado mayor que el de los subprocesos 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 administrado subproceso desde el subproceso nativo al subproceso nativo como lo considere oportuno. La función Thread.BeginThreadAffinity se proporciona para colocar un subproceso administrado en paso de bloqueo con una secuencia de sistema operativo nativa. 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, existe documentation que sugiere que los hilos pueden recibir menos tiempo de procesamiento si están restringidos a un único 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 configurar ProcessorAffinity o incluso establecer un procesador preferido - no estoy seguro de qué se trata.

Descargo de responsabilidad: He tenido un problema similar cuando 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.

17

No es necesariamente tan simple como usar el grupo de subprocesos.

De forma predeterminada, el grupo de subprocesos asigna varios 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 muy limitada caché L1, L2 y quizás L3 de la CPU, etc.), el número óptimo de hilos para usar 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 el número de CPUs:

int processors = 1; 
string processorsStr = System.Environment.GetEnvironmentVariable("NUMBER_OF_PROCESSORS"); 
if (processorsStr != null) 
    processors = int.Parse(processorsStr); 

Por desgracia, no es tan simple como limitación a la cantidad de CPUs. 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 el número óptimo de hilos es un error de prueba. 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.

+3

El número óptimo de hilos para usar es un un poco más que la cantidad de CPU. Tu argumento en sentido contrario es erróneo. Si se produce un cambio de tarea porque un hilo ya no puede avanzar, tendrá ese cambio de tarea independientemente de cuántos hilos haya creado. Los interruptores de tareas del uso completo de un intervalo de tiempo son insignificantes ya que el sistema operativo elige cuidadosamente el intervalo de tiempo para garantizar esto. –

1

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, debe crear sus propios semáforos o mutex ... a qué sistema operativo le da una solución común haciéndolo él mismo .......

2

Definitivamente puede hacerlo escribiendo la rutina dentro de tu programa

Sin embargo, no debe tratar de 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 equilibrar la carga e incluso encontrar un verdadero problema multinúcleo (coreo de datos/coherencia del caché ...) ya que diferentes hilos serían verdaderamente ejecutando en diferente procesador.

Habiendo dicho eso, si aún desea 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 la rutina de arriba se llama, los hilos serían siempre estará ejecutando 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 saber más sobre estos conceptos.

1

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.

Al ejecutar 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) bajo Windows Server 2012, el trabajo se extendió entre todos 12 núcleos, usando alrededor de 8.33% de cada núcleo y nunca disparando 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 llama con -a, establecerá la afinidad en el núcleo 1. La parte de afinidad se basó en this post.

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:

results

velocidad del procesador, como se muestra por el administrador de tareas, de forma similar a lo que los informes de la CPU-Z:

enter image description here

Cuestiones relacionadas