Ein bisschen mehr über schlechte Tests

Eines Tages stieß ich versehentlich auf Code, der besagte, dass ein Benutzer versuchte, die RAM-Leistung seiner virtuellen Maschine zu überwachen. Ich werde diesen Code nicht weitergeben (dort gibt es ein „Fußtuch“) und ich werde nur das Nötigste belassen. Die Katze ist also 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: Weisen Sie Speicher zu und schreiben Sie ein Gigabyte hinein. Und was zeigt dieser Test?

$ ./memtest
4.06504 GB / s

Ungefähr 4 GB/s.

Was?!?!

Wie?!?!?

Das ist Core i7 (wenn auch nicht der neueste), DDR4, der Prozessor ist fast nicht ausgelastet – WARUM?!?!

Die Antwort ist wie immer ungewöhnlich gewöhnlich.

Der new-Operator (wie übrigens auch die malloc-Funktion) reserviert eigentlich keinen Speicher. Bei diesem Aufruf schaut sich der Allokator die Liste der freien Speicherorte im Speicherpool an, und wenn es keine gibt, ruft er sbrk() auf, um das Datensegment zu vergrößern, und gibt dann an das Programm einen Verweis auf die Adresse vom neuen Speicherort zurück zugeteilt.

Das Problem besteht darin, dass der zugewiesene Bereich vollständig virtuell ist. Es werden keine echten Speicherseiten zugewiesen.

Und wenn der erste Zugriff auf jede Seite aus diesem zugewiesenen Segment erfolgt, „schießt“ die MMU einen Seitenfehler, woraufhin der virtuellen Seite, auf die zugegriffen wird, eine reale Seite zugewiesen wird.

Daher testen wir tatsächlich nicht die Leistung der Bus- und RAM-Module, sondern die Leistung der MMU und VMM des Betriebssystems. Und um die tatsächliche Leistung des RAM zu testen, müssen wir die zugewiesenen Bereiche nur einmal 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 zugewiesenen Puffer mit dem Standardwert (char 0).

Wir prüfen:

$ ./memtest
28.5714 GB / s

Eine andere Sache.

Die Moral der Geschichte: Wenn Sie große Puffer benötigen, um schnell arbeiten zu können, vergessen Sie nicht, diese zu initialisieren.

Source: habr.com

Kommentar hinzufügen