Wie kann die Größe der L1-Cache-Zeilengröße mit IO-Timing-Messungen ermittelt werden?

Lesezeit: 8 Minuten

Benutzeravatar von Jiew Meng
Jiew Meng

Als Schulaufgabe muss ich einen Weg finden, die Zeilengröße des L1-Datencaches zu ermitteln, ohne Konfigurationsdateien zu lesen oder API-Aufrufe zu verwenden. Soll Lese-/Schreib-Timings für Speicherzugriffe verwenden, um diese Informationen zu analysieren und abzurufen. Also wie könnte ich das machen?

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

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

Ich dachte, vielleicht brauche ich nur Variationslinie 2, (i * 4) Teil? Sobald ich also die Cache-Line-Größe überschreite, muss ich sie möglicherweise ersetzen, was einige Zeit in Anspruch nimmt? Aber ist es so einfach? Der benötigte Block ist vielleicht schon irgendwo im Speicher? Oder vielleicht kann ich mich immer noch darauf verlassen, dass, wenn ich einen ausreichend großen habe stepswird es trotzdem ziemlich genau klappen?

AKTUALISIEREN

Hier 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 anscheinend keine großen Unterschiede zwischen dem Timing gibt. FYI. da es sich um einen L1-Cache handelt. Ich habe SIZE = 32 K (Größe des Arrays)

  • Das C-Tag wurde hinzugefügt – @JiewMeng, vielleicht würden Sie bestätigen, dass Sie tatsächlich in C schreiben. Ich habe das Hausaufgaben-Tag entfernt (gemäß meta.stackexchange.com/questions/147100/…)

    – Dan Puzey

    1. Oktober 2012 um 14:21 Uhr


  • @DanPuzey, ja, es ist C oder C++ …

    – Jiew Meng

    1. Oktober 2012 um 14:24 Uhr

  • Google “Cache-Benchmarking”, recherchieren Sie.

    – Höchstleistungszeichen

    1. Oktober 2012 um 14:27 Uhr

  • Sie können Assembly und dann die CPUID-Anweisung (es ist eine Prozessoranweisung, keine API) verwenden, um diese Informationen zu erhalten. Ich weiß, dass Sie wahrscheinlich nicht nach einer Lösung wie dieser suchen, aber trotzdem denke ich, dass es sich lohnt, sie zu teilen …

    – Hugo Corra

    5. Oktober 2012 um 14:31 Uhr

  • Diese Frage könnte Ihnen einige Ideen geben. Die Cache-Größen werden nicht gemessen, aber es zeigt erhebliche Leistungseinbußen auf jeder Cache-Ebene.

    – Mystisch

    12. Oktober 2012 um 0:04 Uhr

Benutzeravatar von Alex D
Alex D

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

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

Vergleichen und berechnen Sie, wie viele Bytes/Sekunde Sie mit unterschiedlichen Werten von verarbeiten können n, beginnend bei 1 und gezählt bis etwa 1000. Stellen Sie sicher, dass Ihr Benchmark die berechnete Summe ausgibt, damit der Compiler den Benchmark-Code unmöglich wegoptimieren kann.

Wann n == Ihre Cache-Zeilengröße, jeder Zugriff erfordert das Lesen einer neuen Zeile in den L1-Cache. Die Benchmark-Ergebnisse sollten an diesem Punkt also ziemlich stark langsamer werden.

Wenn das Array groß genug ist, sind die Daten am Anfang des Arrays bereits wieder aus dem Cache, wenn Sie das Ende erreichen, was Sie möchten. Also, nachdem Sie erhöht haben n und neu starten, werden die Ergebnisse nicht dadurch beeinträchtigt, dass benötigte Daten bereits im Cache vorhanden sind.

  • Wahrscheinlich wird das HW-Prefetching Schritte von ‘n’ herausfinden und vor Ihnen laden.

    – auselen

    1. Oktober 2012 um 21:21 Uhr

  • Ich denke, diese Idee sollte funktionieren, aber versuchen Sie, n Schritte auf zufällige Weise zu unternehmen, um ein Vorabrufen zu vermeiden, so etwas wie n + (r * c), wobei c ein Potenzwert von 2 ist, der größer als die mögliche Cache-Zeilengröße ist, und r ein zufälliger Wert ist . Sie müssen sicher sein, dass n + (r * c) in Ihrem Array ist, wahrscheinlich mit Modulo.

    – auselen

    2. Oktober 2012 um 5:42 Uhr


  • Ich denke, es ist auch fair, einige Annahmen zu treffen, wie z. B. die Cache-Zeilengröße muss 2 hoch sein, mindestens 32 Bytes, maximal 512 Bytes.

    – auselen

    2. Oktober 2012 um 5:43 Uhr

  • Dein data Das Array ist nur 32 KB groß, sodass das Ganze in den L1-Cache passt. Bitte achten Sie auf das erste, was ich oben gesagt habe: “Ordnen Sie ein BIG-Char-Array zu. Stellen Sie sicher, dass es zu groß ist, um in den L1- oder L2-Cache zu passen.”.

    – Alex D

    5. Oktober 2012 um 19:21 Uhr

  • @JiewMeng, ich poste meinen Code jetzt nicht, weil Sie mehr erfahren, indem Sie herausfinden, wie Sie Ihren Code selbst reparieren können. Nachdem Sie es herausgefunden haben, kann ich Ihnen meinen Code zum Vergleich senden.

    – Alex D

    6. Oktober 2012 um 4:20 Uhr

auselens Benutzeravatar
auslesen

Schau mal rein Kalibratoralle Arbeiten sind jedoch urheberrechtlich geschützt Quellcode ist frei verfügbar. Von seinem dokumentieren Die Idee, Cache-Line-Größen zu berechnen, klingt viel gebildeter als das, was hier bereits gesagt wurde.

Die unserem Kalibrator-Tool zugrunde liegende Idee ist, einen Mikro-Benchmark zu haben, dessen Leistung nur von der Häufigkeit der auftretenden Cache-Fehler abhängt. Unser Kalibrator ist ein einfaches C-Programm, hauptsächlich eine kleine Schleife, die eine Million Speicherauslesungen ausführt. Indem wir den Stride (dh den Versatz zwischen zwei aufeinanderfolgenden Speicherzugriffen) und die Größe des Speicherbereichs ändern, erzwingen wir unterschiedliche Cache-Miss-Raten.

Grundsätzlich wird das Auftreten von Cache-Miss durch die Array-Größe bestimmt. Array-Größen, die in den L1-Cache passen, erzeugen keine Cache-Fehler, sobald die Daten in den Cache geladen wurden. Analog verursachen Arrays, die die L1-Cachegröße überschreiten, aber immer noch in L2 passen, L1-Fehlschläge, aber keine L2-Fehlschläge. Schließlich verursachen Arrays, die größer als L2 sind, sowohl L1- als auch L2-Fehlschläge.

Die Häufigkeit von Cache-Fehlschlägen hängt von der Zugriffsschrittweite und der Cache-Zeilengröße ab. Bei Schritten, die gleich oder größer als die Cache-Zeilengröße sind, tritt bei jeder Iteration ein Cache-Miss auf. Bei Schritten, die kleiner als die Cache-Zeilengröße sind, tritt ein Cache-Mißerfolg nur alle n Iterationen (im Durchschnitt) auf, wobei n das Verhältnis Cache-Zeilengröße/Schrittweite ist.

Somit können wir die Latenz für einen Cache-Miss berechnen, indem wir die Ausführungszeit ohne Misses mit der Ausführungszeit mit genau einem Miss pro Iteration vergleichen. Dieser Ansatz funktioniert nur, wenn Speicherzugriffe rein sequentiell ausgeführt werden, dh wir müssen sicherstellen, dass sich weder zwei oder mehr Ladebefehle noch Speicherzugriffe und reine CPU-Arbeit überschneiden können. Um dies zu erreichen, verwenden wir einen einfachen Pointer-Chasing-Mechanismus: Der Speicherbereich, auf den wir zugreifen, wird so initialisiert, dass jeder Ladevorgang die Adresse für den nachfolgenden Ladevorgang in der nächsten Iteration zurückgibt. Somit können superskalare CPUs nicht von ihrer Fähigkeit profitieren, die Speicherzugriffslatenz durch spekulative Ausführung zu verbergen.

Um die Cache-Eigenschaften zu messen, führen wir unser Experiment mehrmals durch, wobei wir die Schrittweite und die Array-Größe variieren. Wir stellen sicher, dass der Stride mindestens zwischen 4 Bytes und dem Doppelten der maximal erwarteten Cache-Zeilengröße variiert und dass die Array-Größe von der Hälfte der minimal erwarteten Cache-Größe bis mindestens zum Zehnfachen der maximal erwarteten Cache-Größe variiert.

Ich musste auskommentieren #include "math.h" um es zu kompilieren, danach wurden die Cache-Werte meines Laptops korrekt gefunden. Ich konnte auch keine generierten Postscript-Dateien anzeigen.

  • Für meine Maschine (Haswell) sagt Calibrator die Liniengröße falsch voraus, und der Ansatz von @AlexD funktioniert auch nicht. Das Problem ist der Prefetcher, der es schafft, Muster mit konstanten Schritten zu erraten und das Experiment zu manipulieren. Ich nehme an, dies kann mit deaktiviertem Prefetcher gemessen werden

    – Eli Bendersky

    27. September 2015 um 21:17 Uhr

Benutzeravatar von Tony The Lion
Toni der Löwe

Du kannst den … benutzen CPUID Funktion in Assembler, obwohl nicht portabel, gibt es Ihnen, was Sie wollen.

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

Für AMD-Mikroprozessoren ist die Daten-Cache-Zeilengröße in cl und die Anweisungs-Cache-Zeilengröße ist in dl, nachdem die cpuid-Funktion 0x80000005 aufgerufen wurde.

Davon habe ich das übernommen Artikel hier.

Ich denke, Sie sollten ein Programm schreiben, das das Array in zufälliger Reihenfolge durchläuft, anstatt direkt, da moderne Prozesse Hardware-Vorabrufe durchführen. Erstellen Sie zum Beispiel ein Array von int, dessen Werte die Nummer der nächsten Zelle sind. Ich habe vor 1 Jahr ein ähnliches Programm gemacht http://pastebin.com/9mFScs9Z
Sorry für mein Deutsch, ich bin kein Muttersprachler.

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

  • Speicherbandbreitenabfälle für größere Arrays können Ihnen die Gesamtgröße von L1d / L2 / L3 mitteilen, aber diese Frage fragt nach der Größe jeder Zeile, dh der Cache-Blockgröße.

    – Peter Cordes

    12. Mai 2018 um 4:46 Uhr

Benutzeravatar der Community
Gemeinschaft

Wenn Sie im Schlamm stecken bleiben und nicht herauskommen, schauen Sie nach hier.

Es gibt Handbücher und Code, die erklären, wie Sie das tun, was Sie fragen. Der Code ist auch ziemlich hochwertig. Sehen Sie sich “Unterprogrammbibliothek” an.

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

  • Speicherbandbreitenabfälle für größere Arrays können Ihnen die Gesamtgröße von L1d / L2 / L3 mitteilen, aber diese Frage fragt nach der Größe jeder Zeile, dh der Cache-Blockgröße.

    – Peter Cordes

    12. Mai 2018 um 4:46 Uhr

Benutzeravatar von enTropy
Entropie

Ich denke, es sollte ausreichen, eine Operation zu timen, die eine gewisse Menge an Speicher verwendet. Erhöhen Sie dann nach und nach den Speicher (z. B. Operanden), der von der Operation verwendet wird. Wenn die Betriebsleistung stark nachlässt, haben Sie die Grenze gefunden.

Ich würde einfach ein paar Bytes lesen, ohne sie zu drucken (das Drucken würde die Leistung so stark beeinträchtigen, dass es zu einem Engpass würde). Beim Lesen sollte das Timing direkt proportional zur Menge der gelesenen Bytes sein, bis die Daten nicht mehr in den L1 passen, dann erhalten Sie den Leistungseinbruch.

Außerdem sollten Sie den Speicher einmal zu Beginn des Programms allokieren und bevor Sie mit dem Zählen der Zeit beginnen.

  • Seine Aufgabe besteht nicht darin, die Größe des L1-Cache zu ermitteln, sondern die Größe von eine Cache-Zeile in L1.

    – Alex D

    1. Oktober 2012 um 14:49 Uhr

1394820cookie-checkWie kann die Größe der L1-Cache-Zeilengröße mit IO-Timing-Messungen ermittelt werden?

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

Privacy policy