Ein weiteres Wort zum falschen Testen

Eines Tages begegnete ich zufällig einem Code, mit dem ein Benutzer versuchte, die RAM-Leistung in seiner virtuellen Maschine zu überwachen. Den Code selbst werde ich hier nicht anführen (es handelt sich um eine "Blechmasse") und werde nur das Wesentliche belassen. Also, der Hund im Raum!

#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 ein Gigabyte hinein. Und was zeigt dieser Test?

$ ./memtest
4,06504 GB/s

Ungefähr 4 GB/s.

Was?!?!

Wie?!?!?

Das ist ein Core i7 (wenn auch nicht der neueste), DDR4, der Prozessor ist fast nicht 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 schaut der Allocator sich 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 dann dem Programm einen Verweis auf die Adresse aus dem neu allokierten Block zurück.

Das Problem ist, dass der reservierte Block vollständig virtuell ist. Die realen Seiten des Speichers wurden nicht reserviert.

Und wenn der Zugriff auf jede Seite dieses reservierten Segments das erste Mal erfolgt, löst die MMU einen Page Fault aus, wonach der virtuellen Seite, auf die zugegriffen wird, ein echter Speicher zugewiesen wird.

Tatsächlich testen wir nicht die Leistung des Busses und der RAM-Module, sondern die Leistung des MMU und des VMM des Betriebssystems. Um die tatsächliche Leistung des Arbeitsspeichers zu testen, müssen wir lediglich die zugewiesenen Bereiche 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 die zugewiesenen Puffer mit dem Standardwert (char 0).

Überprüfen wir:

$ ./memtest
28,5714 GB/s

Das ist etwas anderes.

Die Moral der Geschichte – wenn Sie große Puffer benötigen, um schnell zu arbeiten, vergessen Sie nicht, diese zu initialisieren.

Quelle: habr.com

Zuverlässiges Webhosting mit DDoS-Schutz, VPS- und VDS-Server kaufen 🔥 Zuverlässiges Webhosting mit DDoS-Schutz, VPS- und VDS-Server kaufen | ProHoster