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