it-swarm.com.de

So ermitteln Sie die Größe der L1-Cachezeilengröße mit IO Timing-Messungen

Als Schulaufgabe muss ich einen Weg finden, um die Zeilengröße des L1-Datencaches zu ermitteln, ohne Konfigurationsdateien zu lesen oder API-Aufrufe zu verwenden. Angeblich sollten Speicherzugriffe Lese/Schreib-Timings verwenden, um diese Informationen zu analysieren und abzurufen. Wie kann ich das machen? 

In einem unvollständigen Versuch für einen anderen Teil der Zuweisung, um die Ebenen und die Größe des Caches zu ermitteln, habe ich: 

for (i = 0; i < steps; i++) {
    arr[(i * 4) & lengthMod]++;
}

Ich dachte, ich brauche vielleicht nur Zeile 2, (i * 4)-Teil? Sobald ich die Cache-Zeilengröße überschritten habe, muss ich sie möglicherweise ersetzen, was einige Zeit dauert. Aber ist das so einfach? Der benötigte Block befindet sich möglicherweise schon irgendwo im Speicher? Oder perpahs kann ich immer noch darauf zählen, dass ich, wenn ich eine ausreichend große steps habe, immer noch recht genau arbeiten wird? 

UPDATE

Hier ist ein Versuch auf GitHub ... Hauptteil unten

// repeatedly access/modify data, varying the STRIDE
for (int s = 4; s <= MAX_STRIDE/sizeof(int); s*=2) {
    start = wall_clock_time();
    for (unsigned int k = 0; k < REPS; k++) {
        data[(k * s) & lengthMod]++;
    }
    end = wall_clock_time();
    timeTaken = ((float)(end - start))/1000000000;
    printf("%d, %1.2f \n", s * sizeof(int), timeTaken);
}

Das Problem ist, dass es scheinbar keine großen Unterschiede zwischen den Zeitpunkten gibt. Zu Ihrer Information. da es für L1 Cache ist. Ich habe GRÖSSE = 32 K (Arraygröße)

36
Jiew Meng

Weisen Sie ein großes char-Array zu (stellen Sie sicher, dass es zu groß ist, um in den L1 oder L2-Cache zu passen). Füllen Sie es mit zufälligen Daten.

Gehen Sie in n-Bytes über das Array. Machen Sie etwas mit den abgerufenen Bytes, wie zum Beispiel das Summieren.

Vergleichen Sie und berechnen Sie, wie viele Bytes/Sekunde Sie mit unterschiedlichen Werten von n verarbeiten können, beginnend mit 1 und bis zu 1000 zählen. Stellen Sie sicher, dass Ihr Benchmark die berechnete Summe ausgibt, sodass der Compiler den Benchmark-Code nicht optimieren kann.

Wenn n == die Größe der Cache-Zeile ist, muss für jeden Zugriff eine neue Zeile in den L1-Cache eingelesen werden. Die Benchmark-Ergebnisse sollten an diesem Punkt also stark nachlassen.

Wenn das Array groß genug ist, sind die Daten am Anfang des Arrays, sobald Sie das Ende erreicht haben, bereits wieder nicht mehr im Cache, was Sie möchten. Nachdem Sie n inkrementiert und neu gestartet haben, werden die Ergebnisse dadurch nicht beeinträchtigt, dass die erforderlichen Daten bereits im Cache gespeichert sind.

27
Alex D

Werfen Sie einen Blick auf Calibrator , das gesamte Werk ist urheberrechtlich geschützt, aber Quellcode ist frei verfügbar. Von der Idee Dokument , die Größe der Cachezeilen zu berechnen, klingt viel besser als das, was hier bereits gesagt wurde.

Der Grundgedanke unseres Kalibrator-Tools besteht darin, einen Mikro-Benchmark zu haben, dessen Leistung nur davon abhängt auf die Häufigkeit der Cache-Fehler, die auftreten. Unser Kalibrator ist ein einfaches C-Programm, hauptsächlich eine kleine Schleife das führt eine Million Speicherlesevorgänge aus. Durch Ändern des Schrittes (d. H. Des Versatzes zwischen zwei aufeinanderfolgenden Speicherzugriffen) und der Größe des Speicherbereichs erzwingen wir unterschiedliche Cache-Missraten.

Im Prinzip wird das Auftreten von Cache-Fehlern durch die Array-Größe bestimmt. Array-Größen, die in .__ passen. Der L1-Cache generiert keine Cache-Fehler, wenn die Daten in den Cache geladen werden. Analog wird Arrays, die die L1-Cachegröße überschreiten, aber immer noch in L2 passen, führen zu L1-Fehlern, aber keinen L2-Fehlern. Endlich, Arrays, die größer als L2 sind, verursachen sowohl L1 als auch L2.

Die Häufigkeit von Cache-Fehlern hängt vom Zugriffsschritt und der Cache-Zeilengröße ab. Mit Schritten gleich oder größer als die Cache-Zeilengröße, tritt bei jeder Iteration ein Cache-Miss auf. Mit Schritten kleiner als die Cache-Zeilengröße, tritt ein Cache-Miss nur im Durchschnitt alle n Iterationen auf, wobei n .__ ist. der Ratio-Cache Linie Größe/Schritt.

Daher können wir die Latenzzeit für einen Cache-Miss berechnen, indem die Ausführungszeit ohne .__ verglichen wird. Verfehlt die Ausführungszeit mit genau einem Fehlschlag pro Iteration. Dieser Ansatz funktioniert nur, wenn Speicherzugriffe werden rein sequentiell ausgeführt, d. h. wir müssen sicherstellen, dass weder zwei noch mehr Anweisungen, Speicherzugriff und reine CPU-Arbeit können sich überschneiden. Wir verwenden eine einfache Zeigerverfolgung Mechanismus, um dies zu erreichen: Der Speicherbereich, auf den wir zugreifen, wird so initialisiert, dass jeder Ladevorgang die .__ zurückgibt. Adresse für das nachfolgende Laden in der nächsten Iteration. Superskalare CPUs können also nicht von .__ profitieren. ihre Fähigkeit, die Speicherzugriffslatenz durch spekulative Ausführung zu verbergen.

Um die Cache-Eigenschaften zu messen, führen wir unser Experiment mehrmals durch, wobei der Schritt und .__ variiert werden. die Arraygröße. Wir stellen sicher, dass der Schritt mindestens zwischen 4 Bytes und dem doppelten Maximum .__ variiert. erwartete Cachezeilengröße, und die Arraygröße variiert von der Hälfte der minimalen erwarteten Cachegröße bis mindestens das Zehnfache der maximal erwarteten Cachegröße.

Ich musste #include "math.h" auskommentieren, um es kompilieren zu lassen. Danach fand ich die Cache-Werte meines Laptops richtig. Ich konnte auch keine generierten Postscript-Dateien anzeigen.

5
auselen

Sie können die CPUID-Funktion im Assembler verwenden, obwohl sie nicht portierbar ist und Ihnen das gibt, was Sie möchten.

Bei Intel-Mikroprozessoren kann die Cache-Zeilengröße berechnet werden, indem bh mit 8 multipliziert wird, nachdem die cpuid-Funktion 0x1 aufgerufen wurde.

Bei AMD-Mikroprozessoren befindet sich die Daten-Cache-Zeilengröße in cl und die Anweisung Cache-Zeilengröße in DL, nachdem die cpuid-Funktion 0x80000005 aufgerufen wurde.

Ich habe dies aus diesem Artikel hier entnommen .

3
Tony The Lion

Ich denke, Sie sollten ein Programm schreiben, das Array in zufälliger Reihenfolge durchlaufen wird, stattdessen gerade, weil moderne Prozesse Hardware-Prefetch ausführen. __ Machen Sie zum Beispiel Array von int, deren Werte die nächste Zelle sein werden Programm vor 1 Jahr http://Pastebin.com/9mFScs9Z Sorry für mein Englisch, ich bin kein Muttersprachler.

2
Alexey Matveev

Sehen Sie, wie memtest86 implementiert wird. Sie messen und analysieren die Datenübertragungsrate auf irgendeine Weise. Ratenänderungsraten entsprechen der Größe von L1, L2 und der möglichen L3-Cache-Größe.

1
vitaly.v.ch

Wenn Sie im Schlamm stecken bleiben und nicht herauskommen können, schauen Sie hier .

Es gibt Handbücher und Codes, die erklären, wie Sie das tun, was Sie fragen. Der Code ist auch von hoher Qualität. Siehe "Unterprogrammbibliothek".

Der Code und die Handbücher basieren auf X86-Prozessoren.

1
JimR

Ich denke, es sollte ausreichen, um eine Operation auszuführen, die etwas Speicher benötigt. Steigern Sie dann schrittweise den Speicher (beispielsweise Operanden), den die Operation verwendet. Wenn die Operationsleistung stark abnimmt, haben Sie das Limit gefunden.

Ich würde mit dem Lesen einer Reihe von Bytes gehen, ohne sie zu drucken (das Drucken würde die Leistung so beeinträchtigen, dass es zu einem Engpass werden würde). Während des Lesens sollte das Timing direkt proportional zu der Anzahl der gelesenen Bytes sein, bis die Daten nicht mehr in die L1 passen. Dann wird die Performance erreicht.

Sie sollten den Speicher auch einmalig zu Beginn des Programms und vor dem Beginn der Zeitzählung zuordnen.

0
enTropy

Nur eine Notiz.

Die Cache-Zeilengröße ist in einigen ARM Cortex-Familien variabel und kann sich während der Ausführung ändern, ohne dass ein aktuelles Programm benachrichtigt wird.

0
vitaly.v.ch