Frage

Nehmen wir an, ich hätte ein Programm in C#, das etwas rechenintensives erledigt, etwa eine Liste von WAV-Dateien in MP3s zu kodieren.Normalerweise würde ich die Dateien einzeln kodieren, aber sagen wir mal, ich wollte, dass das Programm herausfindet, wie viele CPU-Kerne ich hatte, und auf jedem Kern einen Kodierungsthread startet.Wenn ich das Programm also auf einer Quad-Core-CPU ausführe, stellt das Programm fest, dass es sich um eine Quad-Core-CPU handelt, stellt fest, dass es vier Kerne gibt, mit denen es arbeiten kann, und erzeugt dann vier Threads für die Kodierung, von denen jeder einzeln läuft CPU.Wie würde ich das machen?

Und wäre das anders, wenn die Kerne auf mehrere physische CPUs verteilt wären?Wenn ich beispielsweise eine Maschine mit zwei Quad-Core-CPUs hätte, gibt es dann irgendwelche besonderen Überlegungen oder werden die acht Kerne auf den beiden Chips in Windows als gleich angesehen?

War es hilfreich?

Lösung

Machen Sie sich nicht die Mühe, das zu tun.

Verwenden Sie stattdessen die Thread-Pool.Der Thread-Pool ist ein Mechanismus (eigentlich eine Klasse) des Frameworks, den Sie nach einem neuen Thread abfragen können.

Wenn Sie nach einem neuen Thread fragen, erhalten Sie entweder einen neuen Thread oder die Arbeit wird in die Warteschlange gestellt, bis ein Thread freigegeben wird.Auf diese Weise entscheidet das Framework, ob mehr Threads erstellt werden sollen oder nicht, abhängig von der Anzahl der vorhandenen CPUs.

Bearbeiten:Darüber hinaus ist das Betriebssystem, wie bereits erwähnt, für die Verteilung der Threads auf die verschiedenen CPUs verantwortlich.

Andere Tipps

Es ist nicht unbedingt so einfach wie die Verwendung des Thread-Pools.

Standardmäßig weist der Thread-Pool jeder CPU mehrere Threads zu.Da jeder Thread, der an Ihrer Arbeit beteiligt ist, mit Kosten verbunden ist (Aufwand beim Aufgabenwechsel, Nutzung des sehr begrenzten L1-, L2- und möglicherweise L3-Cache der CPU usw.), ist die optimale Anzahl der zu verwendenden Threads <= die Anzahl der verfügbaren CPUs – es sei denn, jeder Thread fordert Dienste von anderen Maschinen an – beispielsweise einen hoch skalierbaren Webdienst.In einigen Fällen, insbesondere wenn es mehr Lese- und Schreibvorgänge auf der Festplatte als CPU-Aktivität erfordert, können Sie mit einem Thread tatsächlich besser dran sein als mit mehreren Threads.

Für die meisten Anwendungen und insbesondere für die WAV- und MP3-Kodierung sollten Sie die Anzahl der Arbeitsthreads auf die Anzahl der verfügbaren CPUs beschränken.Hier ist ein C#-Code, um die Anzahl der CPUs zu ermitteln:

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

Leider ist es nicht so einfach, sich auf die Anzahl der CPUs zu beschränken.Sie müssen auch die Leistung des/der Festplattencontroller(s) und der Festplatte(n) berücksichtigen.

Die einzige Möglichkeit, die optimale Anzahl an Threads wirklich zu finden, besteht darin, einen Fehler auszuprobieren.Dies gilt insbesondere dann, wenn Sie Festplatten, Webdienste usw. nutzen.Bei Festplatten ist es möglicherweise besser, wenn Sie nicht alle vier Prozessoren Ihrer Quad-Prozessor-CPU verwenden.Andererseits ist es bei einigen Webdiensten möglicherweise besser, 10 oder sogar 100 Anfragen pro CPU zu stellen.

Bei verwalteten Threads ist die Komplexität dieser Vorgehensweise um ein Vielfaches größer als bei nativen Threads.Dies liegt daran, dass CLR-Threads nicht direkt an einen nativen Betriebssystem-Thread gebunden sind.Mit anderen Worten, die CLR kann a schalten gelang es Threads von nativem Thread zu nativem Thread nach eigenem Ermessen verschieben.Die Funktion Thread.BeginThreadAffinity wird bereitgestellt, um einen verwalteten Thread im Lock-Step mit einem nativen Betriebssystem-Thread zu platzieren.An diesem Punkt könnten Sie mit der Verwendung nativer APIs experimentieren, um dem zugrunde liegenden nativen Thread-Prozessor Affinität zu verleihen.Wie jeder hier andeutet, ist das keine sehr gute Idee.Tatsächlich gibt es das Dokumentation Dies deutet darauf hin, dass Threads weniger Verarbeitungszeit benötigen, wenn sie auf einen einzelnen Prozessor oder Kern beschränkt sind.

Sie können auch die erkunden System.Diagnostics.Process Klasse.Dort finden Sie eine Funktion zum Aufzählen der Threads eines Prozesses als Sammlung ProcessThread Objekte.Diese Klasse verfügt über Methoden zum Festlegen der ProcessorAffinity oder sogar zum Festlegen einer bevorzugt Prozessor – ich bin mir nicht sicher, was das ist.

Haftungsausschluss:Ich hatte ein ähnliches Problem, bei dem ich dachte, dass die CPU(s) nicht ausreichend ausgelastet seien, und viele dieser Dinge recherchiert habe;Nach allem, was ich gelesen habe, schien es jedoch keine sehr gute Idee zu sein, wie auch die hier geposteten Kommentare belegen.Dennoch ist es immer noch interessant und eine Lernerfahrung zum Experimentieren.

Obwohl ich den meisten Antworten hier zustimme, denke ich, dass es sich lohnt, eine neue Überlegung hinzuzufügen:Speedstep-Technologie.

Beim Ausführen eines CPU-intensiven Single-Threaded-Jobs auf einem Multi-Core-System, in meinem Fall einem Xeon E5-2430 mit 6 echten Kernen (12 mit HT) unter Windows Server 2012, wurde der Job auf alle 12 Kerne verteilt etwa 8,33 % jedes Kerns und löst nie eine Geschwindigkeitssteigerung aus.Die CPU blieb bei 1,2 GHz.

Wenn ich die Thread-Affinität auf einen bestimmten Kern einstelle, werden etwa 100 % dieses Kerns genutzt, was dazu führt, dass die CPU bei 2,5 GHz ihre maximale Leistung erreicht, was die Leistung mehr als verdoppelt.

Dies ist das Programm, das ich verwendet habe und das nur eine Schleife durchläuft, um eine Variable zu erhöhen.Beim Aufruf mit -a wird die Affinität auf Kern 1 gesetzt.Der Affinitätsteil basierte auf dieser Beitrag.

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);
            }
        }
    }
}

Und die Ergebnisse:

results

Prozessorgeschwindigkeit, wie vom Task-Manager angezeigt, ähnlich dem, was CPU-Z meldet:

enter image description here

Sie sollten sich keine Sorgen darüber machen müssen, dies selbst zu tun.Ich habe Multithread-.NET-Apps, die auf Dual-Quad-Maschinen laufen, und egal, wie die Threads gestartet werden, ob über den ThreadPool oder manuell, ich sehe eine schöne, gleichmäßige Arbeitsverteilung auf alle Kerne.

Sie können dies auf jeden Fall tun, indem Sie die Routine in Ihr Programm schreiben.

Sie sollten dies jedoch nicht versuchen, da das Betriebssystem der beste Kandidat für die Verwaltung dieser Dinge ist.Ich meine, ein Benutzermodusprogramm sollte es nicht versuchen.

Manchmal ist es jedoch möglich (für wirklich fortgeschrittene Benutzer), einen Lastausgleich zu erreichen und sogar ein echtes Multi-Thread-Multi-Core-Problem (Datenrennen/Cache-Kohärenz ...) herauszufinden, da verschiedene Threads tatsächlich auf unterschiedlichen Prozessoren ausgeführt werden .

Wenn Sie jedoch dennoch etwas erreichen möchten, können wir dies auf folgende Weise tun.Ich stelle Ihnen den Pseudocode für (Windows-Betriebssystem) zur Verfügung, er könnte jedoch auch problemlos unter Linux erstellt werden.

#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".

Nachdem die obige Routine aufgerufen wurde, würden die Threads immer auf die folgende Weise ausgeführt:

Thread1-> Core1
Thread2-> Core2
Thread3-> Core3
Thread4-> Core4
Thread5-> Core5
Thread6-> Core6
Thread7-> Core7
Thread8-> Core8

Thread9-> Core1
Thread10-> Core2
...............

Weitere Informationen zu diesen Konzepten finden Sie im Handbuch/MSDN.

Wohin jeder Thread geht, wird im Allgemeinen vom Betriebssystem selbst gehandhabt. Generieren Sie also 4 Threads auf einem 4-Kern-System und das Betriebssystem entscheidet, auf welchen Kernen jeder ausgeführt werden soll, was normalerweise 1 Thread auf jedem Kern ist.

Es ist die Aufgabe des Betriebssystems, Threads auf verschiedene Kerne aufzuteilen. Dies geschieht automatisch, wenn Ihre Threads viel CPU-Zeit beanspruchen.Machen Sie sich darüber keine Sorgen.Versuchen Sie herauszufinden, wie viele Kerne Ihr Benutzer hat Environment.ProcessorCount in C#.

Sie können dies nicht tun, da nur das Betriebssystem über die entsprechenden Berechtigungen verfügt.Wenn Sie sich dazu entschließen, wird es schwierig, Anwendungen zu programmieren.Denn dann müssen Sie sich auch um die Kommunikation zwischen den Prozessoren kümmern.kritische Abschnitte.Für jede Anwendung müssen Sie Ihre eigenen Semaphoren oder Mutex erstellen ... welches Betriebssystem eine gemeinsame Lösung bietet, indem es es selbst durchführt ...

Einer der Gründe, warum Sie (wie bereits gesagt) nicht versuchen sollten, diese Art von Dingen selbst zuzuordnen, ist, dass Sie einfach nicht über genügend Informationen verfügen, um dies ordnungsgemäß zu tun, insbesondere in Zukunft mit NUMA usw.

Wenn Sie einen Thread haben, der zum Ausführen gelesen werden kann, und ein Kern im Leerlauf ist, wird der Kernel Wille Lass deinen Thread laufen, keine Sorge.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top