C# SetAffinity - CPU Zuordnung für mehrere Prozesse mit gleichem Namen

Status
Für weitere Antworten geschlossen.

lynxx

Lt. Junior Grade
Registriert
Feb. 2005
Beiträge
447
Hier eine SetAffinity in C# (braucht NET 2.0).

Beispiel Benutzung:
Code:
C:\Temp>SetAffinity.exe
Usage: SetAffinity <partof>processname <affinity/read>
Done by Holger 'Lynxx' Hippenstiel in March.2020

C:\Temp>SetAffinity.exe explo 3
Process: explorer ID: 4836 Affinity: 3 (Cores: 2 Core0,Core1) Old: 255 (Cores: 8 All Cores)

C:\Temp>SetAffinity.exe explo %110
Process: explorer ID: 4836 Affinity: 6 (Cores: 2 Core1,Core2) Old: 3 (Cores: 2 Core0,Core1)

C:\Temp>SetAffinity.exe explo read
Process: explorer ID: 4836 Affinity: 6 (Cores: 2 Core1,Core2)

C:\Temp>SetAffinity.exe explo
Process: explorer ID: 4836 Affinity: 255 (Cores: 8 All Cores) Old: 3 (Cores: 2 Core0,Core1)

C:\Temp>SetAffinity.exe fxplo
No Process found which contains 'fxplo'

Ausserdem wird die Affinity als ErrorLevel zurückgegeben, so das man in einem Batch z.B Abfragen kann ob die gewünschte Affinty schon zugewiesen ist:
Code:
SetAffinity.exe >NUL explo read
IF %ERRORLEVEL% NEQ 3 (
  SetAffinity.exe >NUL explo 3
)

Code:
using System;
using System.Diagnostics;

namespace SetAffinity {
    class Program {
        static int Main(string[] args) {
            long maxAffinity = (long)(Math.Pow(2, Environment.ProcessorCount) - 1);
            int returnVal = 0;
            if (args == null || args.Length == 0) {
                Console.WriteLine("Usage: SetAffinity <partof>processname <affinity/read>");
                Console.WriteLine("Done by Holger 'Lynxx' Hippenstiel in March.2020");
            }
            else {
                long newAffinity = 0;
                bool bAffinityGiven = false, bReadAffinity = false;
                if (args.Length > 1) {
                    if (args[1].StartsWith("%")) {
                        String strBits = args[1].Substring(1);
                        int j = strBits.Length - 1;
                        bAffinityGiven = true;
                        for (int i = 0; i < strBits.Length; i++, j--) {
                            if (strBits[i] == '1')
                                newAffinity += (long)Math.Pow(2, j);
                            else if (strBits[i] != '0') {
                                Console.WriteLine("SetAffinity-ERROR: Only 0 & 1 allowed in Bitmask !");
                                bAffinityGiven = false;
                                break;
                            }
                        }
                    }
                    else
                        bAffinityGiven = Int64.TryParse(args[1], out newAffinity);

                    if (!bAffinityGiven && args[1].ToLower().Equals("read"))
                        bReadAffinity = true;
                }

                if (!bAffinityGiven)
                    newAffinity = maxAffinity;

                if (newAffinity > maxAffinity) {
                    newAffinity &= maxAffinity;
                    Console.WriteLine("SetAffinity-ERROR: To large Affinity-Mask used, reduced to {0}/{1}", newAffinity, maxAffinity);
                }

                String strProcessName = args[0].ToLower();
                bool bAllProcesses = args[0] == "_ALL_";
                bool bProcessFound = false;

                foreach (Process oneProcess in Process.GetProcesses()) {
                    try {
                        if (oneProcess.ProcessName.ToLower().Contains(strProcessName) || bAllProcesses) {
                            bProcessFound = true;
                            long oldAffinity = (long)oneProcess.ProcessorAffinity;
                            returnVal = (int)oldAffinity;

                            if (oldAffinity == newAffinity || bReadAffinity)
                                Console.WriteLine("Process: {0} ID: {1} Affinity: {2} ({3})", oneProcess.ProcessName, oneProcess.Id, oldAffinity, getCores(oldAffinity));
                            else {
                                oneProcess.ProcessorAffinity = (IntPtr)newAffinity;
                                Console.WriteLine("Process: {0} ID: {1} Affinity: {2} ({3}) Old: {4} ({5})", oneProcess.ProcessName, oneProcess.Id, newAffinity, getCores(newAffinity), oldAffinity, getCores(oldAffinity));
                            }
                        }
                    }
                    catch (Exception) {
                        Console.WriteLine("SetAffinity-ERROR: Failed to set Affinity for Process: {0} ID: {1}", oneProcess.ProcessName, oneProcess.Id);
                    }
                }

                if (!bProcessFound)
                    Console.WriteLine("No Process found which contains '{0}'", strProcessName);
            }
            return returnVal;
        }

        static String getCores(long affinityMask) {
            String strReturn = "";
            bool bAllCores = true;
            int countCores = 0;
            for (int i = 0; i < Environment.ProcessorCount; i++) {
                if ((affinityMask & (long)Math.Pow(2, i)) != 0) {
                    strReturn += "Core" + i + ",";
                    countCores++;
                }
                else
                    bAllCores = false;
            }

            if (bAllCores)
                strReturn = "Cores: " + Environment.ProcessorCount + " All Cores";
            else {
                if (strReturn.EndsWith(","))
                    strReturn = strReturn.Substring(0, strReturn.Length - 1);

                strReturn = "Cores: " + countCores + " " + strReturn;
            }

            return strReturn;
        }
    }
}

Edit: Die erste Version setzte nur bis zu 8 Kernen als Default, diese nimmt die tatsächliche Anzahl des Systems und Ausgabe der aktiven Kerne verbessert ..

2nd Edit: Es kann jetzt _ALL_ als Name angeben werden, dann wird bei allen Prozessen die Affinität eingestellt.
Affinitymaske jetzt in long, danke @cloudman, damit sollte es auch auf 64 Kern-Systemen ohne Probleme laufen und Kern-Ausgabe war vorher bei Systemen mit mehr als 32 Kernen falsch ..

3rd Edit: Die Affinitätsmaske kann jetzt auch mit % als Binärmaske übergeben werden. Wenn die Affinitätsmaske größer als die im System vorhandenen Kerne ist wird sie auf die vorhandenen Kerne beschränkt. Danke an @iamunknown

Virustotal-Scan für die Exe: 1/72
 

Anhänge

Zuletzt bearbeitet:
Für welche App benötigst du eine cpu Zuordnung?
Bitte nicht falsch verstehen soll keine Kritik sein sondern nur Neugier

Hier die Kritik 🙂 : statt die bitmaske in double zu casten könntest du auch long verwenden. Ist zwar im Grunde egal nur sieht double hier ziemlich seltsam aus.

Falls der Hintergrund Systeme mit mehr als 64 cores ist wird es weder mit long noch mit double klappen weil beide nur 64 bit benutzen
 
Huch ja, 64-Bit macht da natürlich mehr Sinn, und ich weiss um das Problem mit mehr als 64 Kernen das Windows dann 2 logische Prozessoren dafür verwendet (z.B Ryzen Threadripper 3990X).
Der Hintergrund war das jemand eine Batch CPU Zuordnung für mehrere Prozesse mit gleichem Namen gesucht hatte, das kann z.B Sinn machen wenn man beim Zocken die letzten 3% aus seinem System rauskitzeln will, indem man z.B einfach alle nicht zum Spiel gehörenden Prozesse auf den letzten Kern legt (System kann man nicht so einfach ändern ..) und das Spiel auf die anderen Kerne beschränkt, oder man hat ein Programm im Hintergrund laufen das man evtl. nicht mit "Start /AFFINITY ..." gleich auf die gewünschten Kerne einschränken kann, mit diesem Tool ist es aber einfach nachträglich möglich.
 
cloudman schrieb:
statt die bitmaske in double zu casten könntest du auch long verwenden. Ist zwar im Grunde egal nur sieht double hier ziemlich seltsam aus.
Die Kritik ist berechtigt, eine Bitmaske sollte man niemals in Floating-Datentypen casten! Entweder int für 32 Bit oder long für 64 Bit (auch bei 32 Bit tauglich). Und dann am Besten auch die bereitgestellten Methoden für den Cast verwenden - für die universelle Lösung ToInt64().
Vor allem wird mit diesem Cast die Ausgabe in der Konsole auf 32 Kerne begrenzt (nicht das Setzen):
C#:
int iAfffinityMask = (int)affinityMask;

cloudman schrieb:
Falls der Hintergrund Systeme mit mehr als 64 cores ist wird es weder mit long noch mit double klappen weil beide nur 64 bit benutzen
Damit ergibt auch die Einteilung in Gruppen bei mehr als 64 logischen CPUs wieder Sinn: Kompatibilität :evillol:. Wird vermutlich eine Struktur vom Kernel sein.

lynxx schrieb:
gleich auf die gewünschten Kerne einschränken kann, mit diesem Tool ist es aber einfach nachträglich möglich.
Nicht ganz universell, dein Tool belegt immer nur die vorderen Kerne. damit wird es nicht möglich z.B. bei 8 Threads mit SetAffinity abc 3 und SetAffinity xyz 5 die beiden Programme auf verschiedene Threads zu verteilen.
 
iamunknown schrieb:
Die Kritik ist berechtigt, eine Bitmaske sollte man niemals in Floating-Datentypen casten! Entweder int für 32 Bit oder long für 64 Bit (auch bei 32 Bit tauglich). Und dann am Besten auch die bereitgestellten Methoden für den Cast verwenden - für die universelle Lösung ToInt64().
Vor allem wird mit diesem Cast die Ausgabe in der Konsole auf 32 Kerne begrenzt (nicht das Setzen):
C#:
int iAfffinityMask = (int)affinityMask;
Ist doch schon alles geändert ..

iamunknown schrieb:
Nicht ganz universell, dein Tool belegt immer nur die vorderen Kerne. damit wird es nicht möglich z.B. bei 8 Threads mit SetAffinity abc 3 und SetAffinity xyz 5 die beiden Programme auf verschiedene Threads zu verteilen.
Da liegt der Fehler aber bei Dir .. 3 = %011 5 = %101 - also benutzen sowohl abc als auch xyz Kern 0 ..
Wenn dann müsste man z.B SetAffinity abc 3 und SetAffinity xyz 12 machen, dann würde abc auf Kern0&1 und xyz auf Kern 2&3 laufen .. Der Affinitywert ist direkt die Bit-Maske für die zu verwendenden Kerne ..
 
lynxx schrieb:
Da liegt der Fehler aber bei Dir
Stimmt - das hatte ich nicht bedacht. Dein Code schreibt die Bitmaske - da wäre dann natürlich noch eine Erweiterung schön wenn man diese auch in Bitschreibweise übergeben kann (bis zum 32 Kern Threadripper läuft das Tool ja noch) und nicht selbst rechnen muss :daumen:.
Wichtiger fände ich jedoch eine kleine Abfrage ob der zu setzende Wert nicht > der verfügbaren CPU Cores ist und generell bei >64 verfügbaren Threads gar nicht erst versuchen etwas zu setzen.
 
iamunknown schrieb:
... da wäre dann natürlich noch eine Erweiterung schön wenn man diese auch in Bitschreibweise übergeben kann (bis zum 32 Kern Threadripper läuft das Tool ja noch) und nicht selbst rechnen muss :daumen:.
Wichtiger fände ich jedoch eine kleine Abfrage ob der zu setzende Wert nicht > der verfügbaren CPU Cores ist und generell bei >64 verfügbaren Threads gar nicht erst versuchen etwas zu setzen.
Beides implementiert: Die Affinitätsmaske kann jetzt auch mit % als Binärmaske übergeben werden. Wenn die Affinitätsmaske größer als die im System vorhandenen Kerne ist wird sie auf die vorhandenen Kerne beschränkt.
Soweit mir bekannt kann Environment.ProcessorCount gar nicht mehr als 64 zurückliefern, und auch andere Abfragen liefern da immer 64 zurück, nur wird der 3990X eben als 2 Prozessoren (NUMA-Nodes) im System eingebunden ..
Wenn mir jemand ein 3990X-System zur Verfügung stellt implementiere ich es gern für mehr als 64 Kerne. :D
 
  • Gefällt mir
Reaktionen: I'm unknown
C#:
String strReturn = "";
bool bAllCores = true;
Die ungarische Notation von Variablen ist veraltet und sollte laut C# Naming Convention nicht verwendet werden.
 
  • Gefällt mir
Reaktionen: RalphS
lynxx schrieb:
Wenn die Affinitätsmaske größer als die im System vorhandenen Kerne ist wird sie auf die vorhandenen Kerne beschränkt.
Die Idee dahinter ist eher dem User eine Warnung zu geben dass seine Konfiguration so vermutlich nicht gewollt ist ;).
 
iamunknown schrieb:
Die Idee dahinter ist eher dem User eine Warnung zu geben dass seine Konfiguration so vermutlich nicht gewollt ist ;).
Gibt es, sieht dann so aus:
SetAffinity.exe explo %111110000 SetAffinity-ERROR: To large Affinity-Mask used, reduced to 240/255 Process: explorer ID: 4836 Affinity: 240 (Cores: 4 Core4,Core5,Core6,Core7) Old: 255 (Cores: 8 All Cores)
 
Zunächst: Net 2.0 ist so weit EOL, daß es schon fast wieder lebig ist. Außerdem ist es inkompatible mit Net 4.x und seit Win8 nicht mehr (standardmäßig) dabei, sodaß man dem Anwender eine veraltete Net-Version aufnötigt, obwohl man es nicht müßte.

=> Target ändern in Net 4 oder, falls praktikabel, Net Core; letzteres wäre portabler.

Als nächstes auch für Dich den Hinweis: C# ist nicht batch. Man kann das natürlich so machen, und ich sehe vor allem auch ein, daß es nicht so schön einfach ist, ein komplettes Projekt hier zu posten (im Vergleich zu einer einzelnen .cs Datei).

Dennoch, ich geh mal davon aus daß Du selber schon gemerkt hast, daß es mit dem code reusage schwierig wird. Das steckt ja alles in main() und die kann man nicht von woanders aufrufen.
Außerdem macht sich das mit dem namespace SetAffinity schwierig, da Du ja im selben Atemzug durchaus auch GetAffinity zuläßt (per Parameter "read").

Deshalb hier der Vorschlag, den Code auseinanderzunehmen (cf refactoring) und zusammenzutun, was zusammengehört: main() für das, was das Programm tut; eine Methode SetAffinity() fürs Setzen und eine GetAffinity fürs Abfragen.
Im Hinblick darauf, daß es da praktisch keine Dynamik gibt, sollte ein statisches Objekt CPUAffinity völlig ausreichen. Dann noch Methoden zum Parsen der Eingabe, wo idealerweise für die Programmlogik ein standardisiertes Ergebnis rumkommt, egal wie die Eingabe beschaffen war. Also zB
C#:
IntPtr GetAffinityMask(long mask) { ... }
IntPtr GetAffinityMask(string bitstring) {... }

Es bleibt Aufgabe von main(), die Eingabe als solches erstmal zu erkennen -- args[] als Eingabe sind alles Strings, aber semantisch ist es halt einmal eine numerische Maske und einmal eine Stringrepräsentation eines Bitfelds. Heck, Du könntest Eingaben ähnlich IPv4 oder MAC-Format erlauben oder sogar als Hexadezimalzahl. Momentan geht so eine Erweiterung kaum.


Zum dritten... wenn Du es Dir zutraust, es gibt die Möglichkeit, per DllImport Funktionalität aus kernel32 zu importieren und so auf ProcessorGroups zuzugreifen. Aber man bricht halt aus managed code aus; eine Sache, die man auch nicht so leichtfertig tun sollte. Wobei ich nicht 100%ig sicher bin, wie es mit aktuellen Net-Versionen aussieht -- mit Net2 ging sehr vieles noch nicht, was dieser Tage selbstverständlich ist.
 
Wow, ich dachte schon den Bullcrap von @pvc-junkie könnte niemand übertreffen, weil ich hier nur ein 5 Minuten-Tool poste das vielleicht für einige nützlich sein könnte, aber @RalphS du schiesst den Bock vollständig ab, erstmal läuft das Programm auch ohne Problem unter Win10, ausserdem ist ja der Quelltext dabei also kann das jeder für sich selbst auf .NET 4.x, Mono oder wasauchimmer kompilieren, ausserdem hatte und werde ich nie den Anspruch haben das der Code reusable/erweiterbar/objektorientiert sein soll .. Geht woanders trollen, mich interessiert euer gelaber nicht. :evillol:
 
Entschuldige bitte, ich werd versuchen, keine Tips mehr für Dich und andere hier bereitzustellen.

Auch wenn es Dir vielleicht nicht bewußt ist, dies ist ein "Programmier"forum, kein "Tool"forum. Alle die hier gucken, nicht nur du und nicht nur ich, sehen diesen Code; nicht jedem sind Spezifika bewußt.

Konstruktive Kritik dann aber als Trolling hinzustellen... nope, das hab ich nicht nötig, erst recht nicht eigene Recherchen ob und ggfs wie mehr als 64 logische CPUs angesprochen werden könnten.

Adíos.
 
  • Gefällt mir
Reaktionen: Ichthys, lexoon, I'm unknown und eine weitere Person
Ich glaube, das hier ist immer noch ein Forum. Wenn man also mit Kritik, wie auch immer geartet, nicht zurechtkommt, dann sollte man gar nicht erst posten. Vorallem wenn man codetechnisch in den 90ern hängen geblieben ist.
Vielleicht solltest du einen Spoiler-Tag um den Code machen, damit nicht noch jemand von den weniger versierten Nutzern denkt, der Schmutz wäre gut.
 
  • Gefällt mir
Reaktionen: Ichthys und lexoon
lynxx schrieb:
Geht woanders trollen, mich interessiert euer gelaber nicht. :evillol:
Dito.
In diesem Ton läuft hier nicht viel.
 
  • Gefällt mir
Reaktionen: Ichthys
Status
Für weitere Antworten geschlossen.
Zurück
Oben