Eines Tages fiel mir zufällig ein Code auf, mit dem ein Benutzer versuchte, die Leistung des RAM in seiner virtuellen Maschine zu überwachen. Den Code werde ich nicht wiedergeben (es ist ein "Schwamm") und lasse nur das Wesentliche übrig. Also, der Kater im Studio!
#include <sys/time.h>
#include <string.h>
#include <iostream>
#define CNT 1024
#define SIZE (1024*1024)
int main() {
struct timeval start;
struct timeval end;
long millis;
double gbs;
char ** buffers;
buffers = new char*[CNT];
for (int i=0;i<CNT;i++) {
buffers[i] = new char[SIZE];
}
gettimeofday(&start, NULL);
for (int i=0;i<CNT;i++) {
memset(buffers[i], 0, SIZE);
}
gettimeofday(&end, NULL);
millis = (end.tv_sec - start.tv_sec) * 1000 +
(end.tv_usec - start.tv_usec) / 1000;
gbs = 1000.0 / millis;
std::cout << gbs << " GB/sn";
for (int i=0;i<CNT;i++) {
delete buffers[i];
}
delete buffers;
return 0;
}Es ist ganz einfach – wir reservieren Speicher und schreiben einen Gigabyte hinein. Und was zeigt dieser Test?
$ .\/memtest
4.06504 GB/s
Ungefähr 4GB/s.
Was?!?!
Wie?!?!?
Das ist ein Core i7 (wenn auch nicht der neueste), DDR4, der Prozessor ist kaum ausgelastet – WARUM?!?!
Die Antwort ist, wie immer, außergewöhnlich gewöhnlich.
Der Operator new (wie auch die Funktion malloc, übrigens) reserviert tatsächlich keinen Speicher. Bei diesem Aufruf sieht der Allocator die Liste der freien Blöcke im Speicherpool an, und wenn keine vorhanden sind, ruft er sbrk() auf, um das Datensegment zu vergrößern, und gibt der Anwendung dann einen Verweis auf die Adresse des gerade reservierten Blocks zurück.
Das Problem ist, dass der reservierte Block rein virtuell ist. Die tatsächlichen Speicherseiten sind nicht reserviert.
Und wenn der erste Zugriff auf jede Seite aus diesem reservierten Segment erfolgt, löst die MMU einen Page Fault aus, nach dem der virtuellen Seite, auf die zugegriffen wird, eine reale Seite zugewiesen wird.
Deshalb testen wir tatsächlich nicht die Leistung des Busses und der RAM-Module, sondern die Leistung der MMU und der VMM des Betriebssystems. Um die tatsächliche Leistung des Arbeitsspeichers zu testen, müssen wir einfach die reservierten Blöcke einmalig initialisieren. Zum Beispiel so:
#include <sys/time.h>
#include <string.h>
#include <iostream>
#define CNT 1024
#define SIZE (1024*1024)
int main() {
struct timeval start;
struct timeval end;
long millis;
double gbs;
char ** buffers;
buffers = new char*[CNT];
for (int i=0;i<CNT;i++) {
// FIXED HERE!!!
buffers[i] = new char[SIZE](); // Add brackets, &$# !!!
}
gettimeofday(&start, NULL);
for (int i=0;i<CNT;i++) {
memset(buffers[i], 0, SIZE);
}
gettimeofday(&end, NULL);
millis = (end.tv_sec - start.tv_sec) * 1000 +
(end.tv_usec - start.tv_usec) / 1000;
gbs = 1000.0 / millis;
std::cout << gbs << " GB/sn";
for (int i=0;i<CNT;i++) {
delete buffers[i];
}
delete buffers;
return 0;
}Das heißt, wir initialisieren einfach die reservierten Puffer mit einem Standardwert (char 0).
Überprüfen:
$ .\/memtest
28.5714 GB/s
Eine andere Sache.
Die Moral – wenn Sie große Puffer benötigen, um schnell zu arbeiten, vergessen Sie nicht, sie zu initialisieren.
Quelle: habr.com
