Un peu plus sur les mauvais tests

Un jour, je suis tombé par hasard sur un code indiquant qu'un utilisateur essayait de surveiller les performances de la RAM dans sa machine virtuelle. Je ne donnerai pas ce code (il y a un « chausson » là-bas) et je ne laisserai que l’essentiel. Voilà, le chat est en 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;
}

C'est simple : allouez de la mémoire et écrivez-y un gigaoctet. Et que montre ce test ?

$ ./test mémoire
4.06504 GB / s

Environ 4 Go/s.

Quoi?!?!

Comment?!?!?

Il s'agit d'un Core i7 (mais pas le plus récent), DDR4, le processeur n'est presque pas chargé - POURQUOI ?!?!

La réponse, comme toujours, est inhabituellement ordinaire.

L'opérateur new (comme la fonction malloc, d'ailleurs) n'alloue pas réellement de mémoire. Avec cet appel, l'allocateur regarde la liste des emplacements libres dans le pool de mémoire, et s'il n'y en a pas, appelle sbrk() pour augmenter le segment de données, puis renvoie au programme une référence à l'adresse du nouvel emplacement juste attribué.

Le problème est que la zone allouée est entièrement virtuelle. Aucune véritable page mémoire n’est allouée.

Et lorsque le premier accès à chaque page à partir de ce segment alloué se produit, la MMU « déclenche » une erreur de page, après quoi la page virtuelle à laquelle on accède se voit attribuer une page réelle.

Par conséquent, en fait, nous ne testons pas les performances des modules de bus et de RAM, mais les performances du MMU et du VMM du système d'exploitation. Et pour tester les performances réelles de la RAM, il suffit d'initialiser une fois les zones allouées. Par exemple comme ceci :

#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;
}

Autrement dit, nous initialisons simplement les tampons alloués avec la valeur par défaut (char 0).

vérifier:

$ ./test mémoire
28.5714 GB / s

Autre chose.

Morale de l'histoire : si vous avez besoin de gros tampons pour travailler rapidement, n'oubliez pas de les initialiser.

Source: habr.com

Ajouter un commentaire