Warum wird in den Speicher geschrieben wird viel langsamer voran, als es zu Lesen?
Hier ist eine einfache memset
Bandbreite benchmark:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
int main()
{
unsigned long n, r, i;
unsigned char *p;
clock_t c0, c1;
double elapsed;
n = 1000 * 1000 * 1000; /* GB */
r = 100; /* repeat */
p = calloc(n, 1);
c0 = clock();
for(i = 0; i < r; ++i) {
memset(p, (int)i, n);
printf("%4d/%4ld\r", p[0], r); /* "use" the result */
fflush(stdout);
}
c1 = clock();
elapsed = (c1 - c0) / (double)CLOCKS_PER_SEC;
printf("Bandwidth = %6.3f GB/s (Giga = 10^9)\n", (double)n * r / elapsed / 1e9);
free(p);
}
Auf meinem system (details siehe unten) mit einem DDR3-1600-Speicher-Modul Ausgänge:
Bandbreite = 4.751 GB/s (Giga = 10^9)
Ist dies 37% der theoretischen RAM-Geschwindigkeit: 1.6 GHz * 8 bytes = 12.8 GB/s
Auf der anderen Seite, hier ist ein ähnliches "Lesen" test:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
unsigned long do_xor(const unsigned long* p, unsigned long n)
{
unsigned long i, x = 0;
for(i = 0; i < n; ++i)
x ^= p[i];
return x;
}
int main()
{
unsigned long n, r, i;
unsigned long *p;
clock_t c0, c1;
double elapsed;
n = 1000 * 1000 * 1000; /* GB */
r = 100; /* repeat */
p = calloc(n/sizeof(unsigned long), sizeof(unsigned long));
c0 = clock();
for(i = 0; i < r; ++i) {
p[0] = do_xor(p, n / sizeof(unsigned long)); /* "use" the result */
printf("%4ld/%4ld\r", i, r);
fflush(stdout);
}
c1 = clock();
elapsed = (c1 - c0) / (double)CLOCKS_PER_SEC;
printf("Bandwidth = %6.3f GB/s (Giga = 10^9)\n", (double)n * r / elapsed / 1e9);
free(p);
}
It-Ausgänge:
Bandbreite = 11.516 GB/s (Giga = 10^9)
Kann ich nah an die theoretische Grenze für die lese-performance, wie XORing eine große Auswahl, aber das schreiben scheint viel langsamer. Warum?
OS Ubuntu 14.04 AMD64 (ich kompiliere mit gcc -O3
. Mit -O3 -march=native
macht die lese-performance etwas schlechter, aber keinen Einfluss auf memset
)
CPU Xeon E5-2630 v2
RAM Einem einzigen "16GB PC3-12800-Parity REG CL11 240-Pin DIMM" (das, Was es sagt, auf der box) ich denke, dass ein einzelnes DIMM-Modul macht die Leistung besser vorhersagbar. Ich gehe davon aus, dass mit 4 DIMMs, memset
werden bis zu 4 mal schneller.
Motherboard Supermicro X9DRG-QF (Unterstützt 4-Kanal-Speicher)
Zusätzliche system: Ein laptop mit 2x 4GB DDR3-1067 RAM: Lesen und schreiben sind beide etwa 5,5 GB/s, aber beachten Sie, dass es verwendet 2 DIMMs.
P. S. ersetzen memset
mit dieser version werden die Ergebnisse in genau der gleichen Leistung
void *my_memset(void *s, int c, size_t n)
{
unsigned long i = 0;
for(i = 0; i < n; ++i)
((char*)s)[i] = (char)c;
return s;
}
printf("%4d/%4ld\r", p[0], r);
in deinem Maßstab bedeutet, dass Sie wahrscheinlich Zeit, eher als alles andere. I/O ist langsam.- Nein!
printf
genannt wird 101 mal in ein Programm, das läuft für 20 Sekunden - Gibt es eine paging-Auftritt?
- In dem code, den Sie geschrieben es sollte als das 100-fache. Es gibt keinen Grund für es zu sein in dem Teil des Codes, den Sie sind benchmarking.
- Es ist gute Praxis, um die "Verwendung" der Ergebnisse Ihrer benchmark-Berechnungen, da sonst der compiler kann zu umgehen, aber die ganze Berechnung (in vielen Fällen, je nach den Besonderheiten). Außerdem bietet es "Fortschritte", so dass Sie wissen, wie lange zu warten.
- Ich versuchte es auf meinem system mit und ohne printf in der Schleife. Der Unterschied war kleiner als ich erwartet hatte (3 mal). Mit, ich habe 9.644, 9.667 und 9.629, ohne bekam ich 9.740, 9.614 und 9.653
- Wahrscheinlich eine Frage der cache-policy (das Prozessor-spezifisch).
- Mein 2010 alte MacBook Berichte 1.937 GB/s ohne Optimierung, und 173010.381 GB/s mit Optimierung mit der gepostet code, unverändert 🙂 wahrscheinlich das memset schreibt, um eine cache-Zeile, die Sie zuerst Lesen aus dem RAM-cache, um verändert zu werden, und dann gespült, so dass jeder cache-Zeile gelesen + geschrieben und nicht nur Lesen. Die Verbleibende Differenz wird wahrscheinlich durch das Lesen von/schreiben auf nicht zusammenhängenden Orten. PowerPC hatte die Anweisungen zum löschen von cache-Zeilen, die geholfen hätten.
- 11.5 GB/s für XORing. Im ernst, printf ist hier vernachlässigbar. Daran habe ich nie gedacht. Ich bin überrascht, die Leute sind besessen mit es hier.
- In jedem Multiprozessor-Umgebung die Aufrechterhaltung der cache-Kohärenz verursacht, schreibt langsamer als liest, insgesamt.
- was ist dein compiler? Unterbindung der Optimierungen in einer trivialen benchmark ist ein interaktiver Prozess. Sie wahrscheinlich benötigen, um die "Verwendung" der Ergebnisse mehr irgendwie.
- Dies ist single-threaded. Wenn Sie denken, dass Ihr Kommentar noch gilt, vielleicht poste es als Antwort?
- Ich fügte hinzu, ein "Lesen" - benchmark auf die Frage.
- es gibt kein swapping (verifiziert mit free-m). Das Programm ordnet 1GB auf einem system mit 16GB RAM
- Ich kann nicht reproduzieren Sie Ihre timing-Unterschied auf meinem Rechner. Im Gegenteil, Ihre xor-Bank ist sogar noch ein bisschen langsamer. Hast du kompilieren mit
-O3 -march=native
? Auch, für die gleiche Optimierung, die klappern in der Lage ist, zur Optimierung der loop völlig aus für diememset
benchmark. - Mit
-O3 -march=native
macht die lese-performance etwas schlechter, aber keinen Einfluss aufmemset
für mich (bearbeitet die Frage) - BTW, Sie sind nicht mess-schreib-performance, aber die performance Ihres
memset
in der C-Bibliothek (vermutlich glibc) auf Ihre Architektur. - Ich wäre daran interessiert, dass dieser benchmark beim kompilieren und ausführen auf einem PC mit FreeDOS-32 als OS. So, der overhead von der virtuellen Speicherverwaltung und paging können dadurch weitgehend vermieden werden.
- CLOCKS_PER_SEC wird ganz gewiss haben Sie den falschen Wert. Moderne Prozessoren bekommen eine mehr oder weniger dynamische Uhr, es kann sehr stark variieren. Man wird Lesen müssen, um den aktuellen (!) Uhr-Wert, der unmittelbar, bevor Sie es verwenden - aber das funktioniert nur, wenn Ihr Programm ist SEHR schnell .. in der Tat, man müsste Lesen Sie den Wert nach JEDEM PROZESSOR-SCHRITT, aber das ist sehr schwer umzusetzen und würde die Ausbeute nur geringe Genauigkeit-Verbesserungen
- CLOCKS_PER_SEC hat einen irreführenden Namen, aber seine (Konstanten) Wert ist definiert der C-standard. Ich bezweifle ernsthaft, dass es "falsch"ist.
- Das ist ein benchmark Vergleich zwischen einem standard-library-Funktion (
memset
) und Ihre eigenen Funktion (do_xor
), nicht zwischen Leseoperation und Schreiboperation. - Ich habe eine version mit meiner eigenen Implementierung
memset
- Wenn der C-library-version von
memset
ist wirklich gleich um die, die Sie geben, ist Ihre installation nicht bekommen, es richtig zu machen. Auf modernen Architekturen, sollte dies anspruchsvoller als die und zu kombinieren, schreibt die nachfolgenden Speicher und Dinge wie, dass. Also das kocht mehr und mehr nach unten, um ein problem mit der Konfiguration dann alles andere. Jede "Antwort" auf deine Frage wäre rein spekulativ. Vielleicht sollten Sie es schließen. - warum ist ein Buch zu schreiben, viel langsamer als das Lesen ein? har... das schreiben ist zu finden zugewiesenen Raum und notieren Sie, wo dieser Raum ist für beim Lesen. Lesen genau anschaut, dass die Platte wie ein Inhaltsverzeichnis und die Erträge auf diesen Standorten, die, wie Sie wissen, können Sie nicht zusammen in einem Stück.
- Für raw-Speicher Lesegeschwindigkeit fand ich es viel genauer zu Lesen, immer 8 bytes in Schritten von 64 bytes (oder wahtever Ihre CPUS cacheline ist). Dies bewirkt, dass alle Speicher übertragen L2, mit minimaler CPU-Auslastung. Ich weiß nicht viel darüber, wie das schreiben funktioniert im detail, aber vielleicht ein ähnlicher Mechanismus kann verwendet werden, um reduzieren alle overhead.
- Diese Frage scheint off-topic, denn es macht schon eine falsche Behauptung in der Frage-Titel. Es ist viel Architektur abhängig und es kann nicht immer eine eindeutige Antwort.
- Es kann sicherlich sein, eine klare Antwort: nämlich, "Ihre Annahme ist falsch, es ist Architektur abhängig und hier sind einige der Faktoren, mit Nachweis". Es ist eine sinnvolle Frage, da dieser Fehleinschätzung kommen immer wieder.
Du musst angemeldet sein, um einen Kommentar abzugeben.
Mit Ihren Programmen, die ich bekommen
auf einen desktop (Core-i7, x86-64, GCC 4.9, GNU libc-2.19) - Maschine mit sechs 2-GB-DIMMs. (Ich habe nicht mehr Details, als dass zu hand, sorry.)
Jedoch diese Programm Berichte schreiben, die die Bandbreite der
12.209 GB/s
:Die Magie ist alles in
_mm_stream_si128
aka the machine instructionmovntdq
, schreibt eine 16-byte-Menge an system-RAM, unter Umgehung des cache (der offizielle jargon für diese ist "non-temporal store"). Ich denke, dass dies ziemlich schlüssig zeigt, dass der performance-Unterschied ist alles über das cache-Verhalten.N. B. glibc 2.19 hat haben ein aufwendig von hand optimiert
memset
macht die Verwendung von Vektor-Anweisungen. Es wird jedoch nicht Verwendung non-temporal stores. Das ist wohl das Richtige fürmemset
; im Allgemeinen, Sie Speicher löschen, kurz bevor Sie es verwenden, so dass Sie wollen es zu heiß in den cache. (Ich nehme an, ein noch klügermemset
umsteigen zu non-temporal stores für wirklich riesig block klar, auf der Theorie, dass Sie könnte nicht wollen, dass alle, die in den cache, denn der cache ist einfach nicht so groß.)(Dies ist in
libc.so.6
, nicht das Programm selbst -- die andere person, die versucht, einen dump der Versammlung fürmemset
scheint nur gefunden zu haben, der seine PLT-Eintrag. Der einfachste Weg, um den assembly dump für die realmemset
auf eine Unixy system ist.)
memset
, vielen Dank für das posten des korrekten Demontage. Und es ist toll zu wissen, dass der trick in der gdb!movnt
Läden geben kann, besser schreiben-Bandbreite für große memsets ist, dass Sie sind schwach bestellt. Sie können überspringen Sie die Lesen-für-ownership Schritt beim schreiben auf eine neue cache-Zeile, denn Sie sind nicht garantiert, werden weltweit sichtbares, um mit jeder anderen oder mit Bezug zu normalen Läden. Auf CPUs mit "fast string operations" (Intel IvB und höher)rep stos
verwendet, etwas schwach bestellt speichert, um die gleiche Beschleunigung haben, aber nicht der cache umgangen. So wie ich das verstehe die docs, es gibt einen store Zaun am Ende der operation, also nur speichern nicht die fahne als Teil von memset/cpy.Den wichtigsten Unterschied in der Leistung kommt aus der caching-Politik von Ihrem PC/memory region. Wenn Sie das Lesen aus einem Speicher, und die Daten nicht im cache, muss der Speicher zuerst geholt, um den cache über den Speicher-bus, bevor Sie durchführen können Berechnungen mit den Daten. Allerdings, wenn Sie in den Speicher schreiben, gibt es verschiedene schreiben Strategien. Höchstwahrscheinlich ist Ihr system mit write-back-cache (oder genauer gesagt "write allocate"), was bedeutet, dass, wenn Sie schreiben in einem Speicherort, der nicht im cache, werden die Daten zuerst holte aus dem Speicher in den cache und irgendwann wieder zurück in den Speicher geschrieben, wenn die Daten entfernt, die aus dem cache, was bedeutet, dass round-trip-für die Daten-und 2x bus-Bandbreite auf schreibt. Es ist auch write-through-caching-Richtlinie (oder "no-write-allocate") die in der Regel bedeutet, dass bei cache-miss auf, schreibt die Daten werden nicht geholt, um den cache, und die sollte näher an die gleiche Leistung für lese-und Schreibvorgänge.
Den Unterschied-zumindest auf meinem Rechner mit einem AMD-Prozessor-ist, dass die lese-Programm nutzt vektorisierte Operationen. Die Dekompilierung der beiden ergibt dies für das schreiben von Programmen:
Aber diese für das Lesen-Programm:
Beachten Sie auch, dass Ihre "homegrown"
memset
ist tatsächlich optimiert unten, um einen Anruf zumemset
:Ich finde keine Referenzen in Bezug auf ob oder nicht
memset
nutzt vektorisierte Operationen, die Demontage vonmemset@plt
ist wenig hilfreich hier:Diese Frage deutet darauf hin, dass seit
memset
ist entworfen, um zu behandeln jeden Fall, es fehlen möglicherweise einige Optimierungen.Dieser Kerl scheint durchaus davon überzeugt, dass Sie brauchen, um zu Rollen Sie Ihre eigenen assembler
memset
um die Vorteile von SIMD-Anweisungen. Diese Frage stellt auch.Ich werde einen Schuss im Dunkeln und denke, dass es keine SIMD-Operationen, weil Sie nicht sagen kann, ob oder nicht es geht um den Betrieb auf etwas, das ein Vielfaches der Größe einer Vektorgrafik-Betrieb, oder ein alignment-Problem.
Jedoch, können wir bestätigen, dass es nicht ein Problem der cache-Effizienz, indem Sie mit
cachegrind
. Das schreiben Programm erzeugt die folgende:und das lese-Programm produziert:
Während die lese-Programm hat eine niedrigere LL-miss-rate, denn es führt viele mehr liest (ein extra Lesen pro
XOR
Betrieb), die Gesamtzahl der findet ist der gleiche. Also, was auch immer das Problem ist, es ist nicht da.This guy definitely seems convinced ...
Seine Puffer ist 244000 mal kleiner und passt in verschiedene caches.Caching und Lokalität fast sicher erklären, die meisten der Effekte, die Sie sehen.
Gibt es keine Zwischenspeicherung oder der Lokalität auf, schreibt, es sei denn, Sie wollen ein nicht-deterministisches system. Die meisten schreiben mal gemessen als die Zeit, die es dauert, die Daten zu bekommen, die alle den Weg auf das Speichermedium (ob das eine Festplatte oder ein Speicher-chip), in der Erwägung, dass liest kann aus einer beliebigen Anzahl von cache-Ebenen, die schneller sind als das Speichermedium.
do_xor
läuft die zweite Zeit, alle zuvor zwischengespeicherten Werte wurden gestrichen. Neben der Zwischenspeicherung erklären könnte, Lesen schneller als die DRAM->Cache link (falls dies der Fall ist). Es erklärt nicht schreiben wird langsamer.movnt
) stark bestellt. In einem schreiben an einen kalten cache-line Trigger-Lesen-für-Eigentum. Wie ich es verstehe, ist die CPU wirklich macht ein Lesen vom DRAM (oder niedrigeren level cache) zum füllen des cache-Zeile. Schreibt härter sind als Lesevorgänge für ein system mit stark bestellt Speicher (wie x86), aber nicht für den Grund, den Sie geben. Shops erlaubt sind, gepuffert werden und zu weltweit sichtbar, nachdem die Lasten von den selben thread. (MFENCE ist ein StoreLoad Schranke...) die AMD ist die Verwendung von write-through-caches für Einfachheit, aber Intel verwendet die write-back für bessere Leistung.movnt
und Schnelle String-rep movsb schwach-bestellten Geschäfte vermeiden, die RFO.Könnte es Nur, Wie es (das-System-as-a-Ganzes) Führt. Das Lesen wird schneller scheint ein allgemeiner trend mit einem Breite Palette der relative Durchsatz-performance. Auf einem schnelle Analyse der DDR3 Intel und DDR2-charts aufgeführt, als einigen wenigen Fällen (schreiben/Lesen)%;
Einige top-performing-DDR3-chips sind schriftlich bei ~60-70% der lese-Durchsatz. Es gibt jedoch einige Speichermodule (ie. Das goldene Reich CL11-13-13-D3-2666) bis zu nur ~30% schreiben.
Top-performing-DDR2-chips scheinen nur etwa ~50% der write-Durchsatz im Vergleich zu Lesen. Aber es gibt auch einige, insbesondere bad-Anwärter (ie. OCZ OCZ21066NEW_BT1G) bis zu ~20%.
Dies ist zwar nicht erklären die Ursache für die ~40% write/read berichtete, wie benchmark-code und setup verwendet, ist wahrscheinlich anders (die Noten sind vage), dies ist definitiv eine Faktor. (Ich würde einige der bestehenden benchmark-Programme und sehen, wenn die zahlen fallen in eine Linie mit denen des Kodex in Frage.)
Update:
Ich habe die memory-look-up-Tabelle aus der verknüpften Seiten und bearbeitet es in Excel. Während es zeigt immer noch eine Breite Palette von Werten ist es viel weniger arg als die ursprüngliche Antwort oben, die schauten nur auf die top-lese-Speicher-chips und ein paar ausgewählte "interessante" Einträge aus den charts. Ich bin mir nicht sicher, warum die Unterschiede, vor allem in der schrecklichen Anwärter herausgegriffen oben, sind nicht in die sekundäre Liste.
Jedoch auch unter der neuen zahlen wird der Unterschied noch reicht überall von 50%-100% (median 65, Mittelwert 65) der lese-performance. Bitte beachten Sie, dass, nur weil ein chip war "100%" effizient in einem schreib - /lese-Verhältnis bedeutet nicht, es war insgesamt besser .. nur, dass es mehr selbst-Kiel zwischen den beiden Operationen.
Hier ist meine Arbeitshypothese. Wenn das stimmt, erklärt es, warum schreibt etwa zweimal langsamer als liest:
Obwohl
memset
nur schreibt, um den virtuellen Speicher ignoriert die vorherigen Inhalte, die auf hardware-Ebene der computer nicht ein reines schreiben DRAM: es liest den Inhalt des DRAM in den cache aus, modifiziert es und dann schreibt Sie wieder zu DRAM. Daher, die auf hardware-Ebenememset
bedeutet sowohl Lesen, als auch schreiben (obwohl der ehemalige scheint nutzlos)! Daher die ungefähr zwei-Fach-speed-Unterschied.movnt
oder Intel IvB-und-späterrep stos
/rep movs
"Fast String Operations"). Es nervt, dass es nicht eine bequeme Möglichkeit schwach-bestellt-Shops (außer memset/memcpy auf aktuelle Intel CPUs) ohne auch unter Umgehung des cache. Ich verließ ähnliche Kommentare auf einige andere Antworten: der Hauptgrund für normal schreibt auslösen liest ist x86 ist stark-bestellt-Speicher-Modell. Begrenzen Sie Ihr system, um ein DIMM oder nicht, sollte nicht ein Faktor in diesem.Da Lesen Sie einfach Puls obigen Zeilen, die Adresse und das Auslesen der core-Staaten auf die Sinn-Linien. Die write-back-Zyklus Auftritt, nachdem die Daten geliefert, um die CPU und damit auch nicht die Dinge verlangsamen. Auf der anderen Seite, zu schreiben, müssen Sie zuerst eine gefälschte Lesen Sie zurücksetzen, um die Kerne, dann führen Sie den write-Zyklus.
(Nur, falls es nicht offensichtlich ist diese Antwort tongue-in-cheek -- beschreiben warum schreiben langsamer als das Lesen auf einem alten core-memory-box.)