Wie kann ich die CPU-Frequenz programmgesteuert mit C finden?

Lesezeit: 12 Minuten

Benutzer-Avatar
Mike

Ich versuche herauszufinden, ob es irgendwie möglich ist, eine Vorstellung von der CPU-Frequenz des Systems zu bekommen, auf dem mein C-Code läuft.

Zur Verdeutlichung suche ich nach einer abstrakten Lösung (eine, die nicht an eine bestimmte Architektur oder ein bestimmtes Betriebssystem gebunden ist), die mir eine Vorstellung von der Betriebsfrequenz des Computers geben kann, auf dem mein Code ausgeführt wird. Ich muss nicht genau sein, aber ich würde gerne im Stadion sein (dh ich habe einen 2,2-GHz-Prozessor, ich möchte in meinem Programm sagen können, dass ich innerhalb von ein paar Hundert bin MHz davon)

Hat jemand eine Idee, Standard-C-Code zu verwenden?

  • Erfinden Sie das Rad nicht neu. Das Betriebssystem verwaltet die Hardware und verfügt bereits über diese Funktionalität. Finden Sie also einen Weg, um zu erkennen, auf welchem ​​​​Betriebssystem das Programm ausgeführt wird, und extrahieren Sie dann die CPU-Frequenz entsprechend.

    – Alex W

    29. Juli 2012 um 4:14 Uhr

  • Das ist im Grunde bedeutungslos. Angenommen, Sie haben ein Programm, das unter einem modernen Multitasking-Betriebssystem ausgeführt wird und auf einem virtuellen Cloud-Server installiert ist. Was bedeutet Taktfrequenz? Selbst wenn Bare-Metal auf einem Mikrocontroller mit deaktivierten Interrupts ausgeführt wird, ohne internen Speicher mit Wartezustand Null, welche Relevanz hat die “Taktgeschwindigkeit”, ohne zu wissen, mit welchen Anweisungen Ihr Programm kompiliert ist und wie viele Taktzyklen jeweils erforderlich sind?

    – Chris Stratton

    29. Juli 2012 um 4:31 Uhr

  • Dieses Thema könnte Sie inspirieren: stackoverflow.com/questions/2814569/… Grüße.

    – Inhaltsverzeichnis

    29. Juli 2012 um 4:45 Uhr

  • Du kannst nicht. Standard C (definiert durch ein normatives Dokument in Englisch) sollte nicht einmal auf Hardware ausgeführt werden – Sie können in einem Emulator ausgeführt werden oder unethischerweise ein Team menschlicher Sklaven verwenden, um Ihren Code zu interpretieren. Der Begriff der CPU und ihrer Frequenz hat also weniger Bedeutung Standard-C. Natürlich gibt es für bestimmte Betriebssysteme und APIs einige spezifische Antworten. (Lesen Sie unter Linux der Reihe nach /proc/cpuinfo)

    – Basile Starynkevitch

    20. August 2014 um 8:39 Uhr


  • Ich denke, viele der Leute auf SO würden den Turing-Test nicht bestehen. Alles, was leicht mehrdeutig ist, gibt einen Syntaxfehler zurück. Ich habe eine Lösung gefunden, um die Betriebsfrequenz für einen echten Intel-Prozessor (keinen virtuellen) mithilfe von C/C++ mit Intrinsic zu ermitteln, und die Leute diskutieren, was Standard C ist. Werde ich Programmierer jemals verstehen? Interessiert sich noch jemand für Hardware?

    – Z-Boson

    20. August 2014 um 15:29 Uhr

Der Vollständigkeit halber gibt es bereits eine einfache, schnelle, genaue Benutzermoduslösung mit einem großen Nachteil: Sie funktioniert nur auf Intel Skylake, Kabylake und neueren Prozessoren. Die genaue Anforderung ist die Unterstützung von CPUID Level 16h. Gemäß Intel Software Developer’s Manual 325462 Version 59, Seite 770:

  • CPUID.16h.EAX = Prozessorbasisfrequenz (in MHz);

  • CPUID.16h.EBX = Maximalfrequenz (in MHz);

  • CPUID.16h.ECX = Bus-(Referenz-)Frequenz (in MHz).

Visual Studio 2015-Beispielcode:

#include <stdio.h>
#include <intrin.h>

int main(void) {
    int cpuInfo[4] = { 0, 0, 0, 0 };
    __cpuid(cpuInfo, 0);
    if (cpuInfo[0] >= 0x16) {
        __cpuid(cpuInfo, 0x16);

        //Example 1
        //Intel Core i7-6700K Skylake-H/S Family 6 model 94 (506E3)
        //cpuInfo[0] = 0x00000FA0; //= 4000 MHz
        //cpuInfo[1] = 0x00001068; //= 4200 MHz
        //cpuInfo[2] = 0x00000064; //=  100 MHz

        //Example 2
        //Intel Core m3-6Y30 Skylake-U/Y Family 6 model 78 (406E3)
        //cpuInfo[0] = 0x000005DC; //= 1500 MHz
        //cpuInfo[1] = 0x00000898; //= 2200 MHz
        //cpuInfo[2] = 0x00000064; //=  100 MHz

        //Example 3
        //Intel Core i5-7200 Kabylake-U/Y Family 6 model 142 (806E9)
        //cpuInfo[0] = 0x00000A8C; //= 2700 MHz
        //cpuInfo[1] = 0x00000C1C; //= 3100 MHz
        //cpuInfo[2] = 0x00000064; //=  100 MHz

        printf("EAX: 0x%08x EBX: 0x%08x ECX: %08x\r\n", cpuInfo[0], cpuInfo[1], cpuInfo[2]);
        printf("Processor Base Frequency:  %04d MHz\r\n", cpuInfo[0]);
        printf("Maximum Frequency:         %04d MHz\r\n", cpuInfo[1]);
        printf("Bus (Reference) Frequency: %04d MHz\r\n", cpuInfo[2]);
    } else {
        printf("CPUID level 16h unsupported\r\n");
    }
    return 0;
}

  • Meldet es die aktuelle Frequenz irgendwo in der CPUID?

    – osgx

    14. März 2017 um 5:42 Uhr

  • Dadurch wird nur die “auf der Box geschriebene” Frequenz gemeldet, die sich etwas von der wahren Frequenz unterscheiden kann, da die “Referenzfrequenz” nur ein Nennwert ist (z. B. kann die tatsächliche BCLK um einen erheblichen Betrag von der Referenz von beispielsweise 100 MHz abweichen). ) und kann sein wild unterschiedlich aufgrund automatischer Frequenzskalierung (Turbo, Speedstep usw.) oder manueller Frequenzbegrenzungen (z. B. durch das Betriebssystem aufgrund von Energieeinsparung auferlegt) und mehreren anderen Gründen.

    – BeeOnRope

    27. Dezember 2017 um 21:34 Uhr

Benutzer-Avatar
Z-Boson

Es ist möglich, eine allgemeine Lösung zu finden, die die Betriebsfrequenz für einen Thread oder viele Threads korrekt erhält. Dies erfordert keine Administrator-/Root-Rechte oder Zugriff auf modellspezifische Register. Ich habe dies unter Linux und Windows auf Intel-Prozessoren getestet, darunter Nahalem, Ivy Bridge und Haswell mit einem Socket bis zu vier Sockets (40 Threads). Die Ergebnisse weichen alle weniger als 0,5 % von den richtigen Antworten ab. Bevor ich Ihnen zeige, wie das geht, lassen Sie mich die Ergebnisse zeigen (von GCC 4.9 und MSVC2013):

Linux:    E5-1620 (Ivy Bridge) @ 3.60GHz    
1 thread: 3.789, 4 threads: 3.689 GHz:  (3.8-3.789)/3.8 = 0.3%, 3.7-3.689)/3.7 = 0.3%

Windows:  E5-1620 (Ivy Bridge) @ 3.60GHz
1 thread: 3.792, 4 threads: 3.692 GHz: (3.8-3.789)/3.8 = 0.2%, (3.7-3.689)/3.7 = 0.2%

Linux:  4xE7-4850 (Nahalem) @ 2.00GHz
1 thread: 2.390, 40 threads: 2.125 GHz:, (2.4-2.390)/2.4 = 0.4%, (2.133-2.125)/2.133 = 0.4%

Linux:    i5-4250U (Haswell) CPU @ 1.30GHz
1 thread: within 0.5% of 2.6 GHz, 2 threads wthin 0.5% of 2.3 GHz

Windows: 2xE5-2667 v2 (Ivy Bridge) @ 3.3 GHz
1 thread: 4.000 GHz, 16 threads: 3.601 GHz: (4.0-4.0)/4.0 = 0.0%, (3.6-3.601)/3.6 = 0.0%

Die Idee dazu habe ich über diesen Link
http://randomascii.wordpress.com/2013/08/06/defective-heat-sinks-causing-garbage-gaming/

Um dies zu tun, tun Sie zunächst das, was Sie vor 20 Jahren getan haben. Sie schreiben einen Code mit einer Schleife, bei der Sie die Latenz und die Zeit kennen. Hier ist, was ich verwendet habe:

static int inline SpinALot(int spinCount)
{
    __m128 x = _mm_setzero_ps();
    for(int i=0; i<spinCount; i++) {
        x = _mm_add_ps(x,_mm_set1_ps(1.0f));
    }
    return _mm_cvt_ss2si(x);
}

Dies hat eine Abhängigkeit von einer getragenen Schleife, sodass die CPU dies nicht neu ordnen kann, um die Latenz zu reduzieren. Es dauert immer 3 Taktzyklen pro Iteration. Das Betriebssystem wird den Thread nicht auf einen anderen Kern migrieren, da wir die Threads binden.

Dann führen Sie diese Funktion auf jedem physischen Kern aus. Ich habe das mit OpenMP gemacht. Dazu müssen die Fäden gebunden werden. Unter Linux mit GCC können Sie verwenden export OMP_PROC_BIND=true die Fäden zu binden und davon auszugehen, dass Sie haben ncores physischer Kern auch export OMP_NUM_THREADS=ncores. Wenn Sie die Anzahl der physischen Kerne für Intel-Prozessoren programmgesteuert binden und finden möchten, lesen Sie diese programmgesteuerte Erkennung der Anzahl physischer Prozessorkerne oder Hyper-Threading und Thread-Affinität mit Windows-msvc- und-openmp.

void sample_frequency(const int nsamples, const int n, float *max, int nthreads) {
    *max = 0;
    volatile int x = 0;
    double min_time = DBL_MAX;
    #pragma omp parallel reduction(+:x) num_threads(nthreads)
    {
        double dtime, min_time_private = DBL_MAX;
        for(int i=0; i<nsamples; i++) {
             #pragma omp barrier
             dtime = omp_get_wtime();
             x += SpinALot(n);
             dtime = omp_get_wtime() - dtime;
             if(dtime<min_time_private) min_time_private = dtime;
        }
        #pragma omp critical
        {
            if(min_time_private<min_time) min_time = min_time_private;
        }
    }
    *max = 3.0f*n/min_time*1E-9f;
}

Lassen Sie den Sampler schließlich in einer Schleife laufen und drucken Sie die Ergebnisse aus

int main(void) {
    int ncores = getNumCores();
    printf("num_threads %d, num_cores %d\n", omp_get_max_threads(), ncores);       
    while(1) {
        float max1, median1, max2, median2;
        sample_frequency(1000, 1000000, &max2, &median2, ncores);
        sample_frequency(1000, 1000000, &max1, &median1,1);          
        printf("1 thread: %.3f, %d threads: %.3f GHz\n" ,max1, ncores, max2);
    }
}

Ich habe dies nicht auf AMD-Prozessoren getestet. Ich denke, AMD-Prozessoren mit Modulen (z. B. Bulldozer) müssen an jedes Modul binden, nicht an jeden AMD-“Kern”. Damit könnte man fertig werden export GOMP_CPU_AFFINITY mit GCC. Ein vollständiges Arbeitsbeispiel finden Sie unter https://bitbucket.org/zboson/frequency das unter Windows und Linux auf Intel-Prozessoren funktioniert und die Anzahl der physischen Kerne für Intel-Prozessoren (zumindest seit Nahalem) korrekt findet und sie an jeden physischen Kern bindet (ohne Verwendung von OMP_PROC_BIND die MSVC nicht hat).


Diese Methode muss für moderne Prozessoren aufgrund der unterschiedlichen Frequenzskalierung für SSE, AVX und AVX512 etwas modifiziert werden.

Hier ist eine neue Tabelle, die ich bekomme, nachdem ich meine Methode (siehe Code nach der Tabelle) mit vier Xeon 6142-Prozessoren (16 Kerne pro Prozessor) geändert habe.

        sums  1-thread  64-threads
SSE        1       3.7         3.3
SSE        8       3.7         3.3
AVX        1       3.7         3.3
AVX        2       3.7         3.3
AVX        4       3.6         2.9
AVX        8       3.6         2.9
AVX512     1       3.6         2.9
AVX512     2       3.6         2.9
AVX512     4       3.5         2.2
AVX512     8       3.5         2.2

Diese Zahlen stimmen mit den Häufigkeiten in dieser Tabelle überein
https://en.wikichip.org/wiki/intel/xeon_gold/6142#Frequenzen

Das Interessante ist, dass ich jetzt mindestens 4 parallele Summen machen muss, um die niedrigeren Frequenzen zu erreichen. Die Latenz für Addps auf Skylake beträgt 4 Taktzyklen. Diese können an zwei Ports gehen (mit AVX512-Ports 0 und 1 Sicherung zum Zählen und ein AVX512-Port und die anderen AVX512-Operationen gehen an Port 5).

So habe ich acht parallele Summen gemacht.

static int inline SpinALot(int spinCount) {
  __m512 x1 = _mm512_set1_ps(1.0);
  __m512 x2 = _mm512_set1_ps(2.0);
  __m512 x3 = _mm512_set1_ps(3.0);
  __m512 x4 = _mm512_set1_ps(4.0);
  __m512 x5 = _mm512_set1_ps(5.0);
  __m512 x6 = _mm512_set1_ps(6.0);
  __m512 x7 = _mm512_set1_ps(7.0);
  __m512 x8 = _mm512_set1_ps(8.0);
  __m512 one = _mm512_set1_ps(1.0);
  for(int i=0; i<spinCount; i++) {
    x1 = _mm512_add_ps(x1,one);
    x2 = _mm512_add_ps(x2,one);
    x3 = _mm512_add_ps(x3,one);
    x4 = _mm512_add_ps(x4,one);
    x5 = _mm512_add_ps(x5,one);
    x6 = _mm512_add_ps(x6,one);
    x7 = _mm512_add_ps(x7,one);
    x8 = _mm512_add_ps(x8,one);
  }
  __m512 t1 = _mm512_add_ps(x1,x2);
  __m512 t2 = _mm512_add_ps(x3,x4);
  __m512 t3 = _mm512_add_ps(x5,x6);
  __m512 t4 = _mm512_add_ps(x7,x8);
  __m512 t6 = _mm512_add_ps(t1,t2);
  __m512 t7 = _mm512_add_ps(t3,t4);
  __m512  x = _mm512_add_ps(t6,t7);
  return _mm_cvt_ss2si(_mm512_castps512_ps128(x));
}

  • Das hat Skylake gebrochen, wodurch sich die Latenz verändert hat addps

    – Harald

    18. Juni 2016 um 21:09 Uhr

  • Möglicherweise sind Sie mit einer einfachen Anweisung mit einer Latenz von 1 Zyklus besser dran, da dies in Zukunft wahrscheinlich nicht schlechter (oder besser!) wird. Zum Beispiel eine Kette abhängiger Adds. Ein Problem ist, dass Sie sicherstellen müssen, dass Sie entweder die Vektorisierung deaktivieren oder Ihre Schleife für die Vektorisierung ungeeignet machen, damit sie zuverlässig ist …

    – BeeOnRope

    28. Mai 2017 um 3:52 Uhr

  • @Zboson – Ich verwende eine abhängige Schleife add Anweisungen zum Messen der CPU-Geschwindigkeit hier und es scheint gut zu funktionieren, normalerweise innerhalb von 1 oder 2 MHz auf meinem Computer. Natürlich muss Turbo sowie DVFS deaktiviert werden, oder alle Wetten sind ausgeschaltet.

    – BeeOnRope

    2. Juni 2017 um 0:03 Uhr

  • Das soll nicht heißen, dass Sie selbst mit diesen Dingern keine halbwegs vernünftigen Ergebnisse erzielen können; insbesondere werden die subnominalen Frequenzen größtenteils eliminiert, wenn Sie einige 100 ms lang etwas mit 100 % CPU ausgeführt haben. Turbo können Sie jedoch nie lösen (es sei denn, Sie ändern die Multiplikatoren, während ich msr regs schreibe), und in meinem Fall erhöht das Deaktivieren von Turbo die Stabilität der Messungen um zwei Größenordnungen. Ohne Turbo kann ich zeitbasierte Messungen bis auf etwa 0,1 % oder 0,01 % erhalten, aber mit Turbo beträgt der Fehler normalerweise mehr als 1 %. @Zboson

    – BeeOnRope

    2. Juni 2017 um 7:15 Uhr


  • @PeterCordes- Hier ist, was ich meine für die schnelle Fib. fib2 berechnet die fib-Sequenz (verifiziert über die printfs), aber die Abhängigkeitsketten sind halb so lang. Beachten Sie, dass gcc “macht” meine gute Arbeit “rückgängig” und verwendet Klartext add aber clang und gcc verwenden lea. Beachten Sie, dass ich die Schleife noch 2x entrollt habe, um die beiden Phasen der Berechnung auszudrücken, aber Sie könnten dies auch mit einem Temporär tun a Variable. Ich habe es als hinzugefügt fib3 was eher der Version entspricht, die Sie geschrieben haben. Die Compiler kommen damit allerdings nicht gut zurecht.

    – BeeOnRope

    27. Dezember 2017 um 22:44 Uhr


Benutzer-Avatar
Yunchi

Wie Sie die CPU-Frequenz finden, hängt sowohl von der Architektur als auch vom Betriebssystem ab, und es gibt keine abstrakte Lösung.

Wenn wir vor mehr als 20 Jahren waren und Sie ein Betriebssystem ohne Kontextwechsel verwendeten und die CPU die ihr gegebenen Anweisungen der Reihe nach ausführte, könnten Sie C-Code in einer Schleife schreiben und ihn zeitlich festlegen, dann basierend auf der Assembly, in die er kompiliert wurde Berechnen Sie die Anzahl der Anweisungen zur Laufzeit. Dies setzt bereits die Annahme voraus, dass jeder Befehl 1 Taktzyklus benötigt, was seit Pipeline-Prozessoren eine ziemlich schlechte Annahme ist.

Aber jedes moderne Betriebssystem wechselt zwischen mehreren Prozessen. Selbst dann können Sie versuchen, ein paar identische Zeiten zu messen for Schleifenläufe (unter Berücksichtigung der Zeit, die für Seitenfehler und mehrere andere Gründe, warum Ihr Prozessor stehen bleiben könnte, benötigt wird) und erhalten Sie einen Mittelwert.

Und selbst wenn die vorherige Lösung funktioniert, haben Sie Multi-Issue-Prozessoren. Bei jedem modernen Prozessor ist es fair, Ihre Anweisungen neu zu ordnen, eine Reihe von ihnen im selben Taktzyklus auszugeben oder sie sogar auf Kerne aufzuteilen.

  • Ja, so ungefähr habe ich mir das gedacht. Ich habe nur die Daumen gedrückt, dass ich etwas Dummes übersehen habe. Eine Möglichkeit, das Wechseln von Aufgaben zu verhindern, die CPU zu zwingen, einen einzelnen Kontext auszuführen, und eine Messung durchzuführen … oder etwas in dieser Richtung. Zu viel verlangen. Danke für die Eingabe.

    – Mike

    30. Juli 2012 um 3:38 Uhr

Die CPU-Frequenz ist eine hardwarebezogene Sache, daher gibt es keine allgemeine Methode, die Sie anwenden können, um sie zu erhalten, sie hängt auch vom verwendeten Betriebssystem ab.

Wenn Sie beispielsweise Linux verwenden, können Sie entweder die Datei lesen /proc/cpuinfo oder Sie können die analysieren dmesg boot log, um diesen Wert zu erhalten, oder wenn Sie möchten, können Sie hier sehen, wie der Linux-Kernel mit diesem Zeug umgeht, und versuchen, den Code an Ihre Bedürfnisse anzupassen:

https://github.com/torvalds/linux/blob/master/arch/x86/kernel/cpu/proc.c

Grüße.

Ich denke, eine Möglichkeit, die Taktfrequenz von Software zu erhalten, besteht darin, Kenntnisse des Hardware Reference Manual (HRM) in Software fest zu codieren. Sie können die Uhrenkonfigurationsregister aus der Software lesen. Unter der Annahme, dass Sie die Quelltaktfrequenz kennen, kann die Software die Multiplikator- und Teilerwerte aus den Taktregistern verwenden und geeignete Formeln anwenden, wie in HRM erwähnt, um die Taktfrequenz abzuleiten.

1215700cookie-checkWie kann ich die CPU-Frequenz programmgesteuert mit C finden?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy