Un jour, je suis tombé par hasard sur un code qu'un utilisateur utilisait pour surveiller la performance de la RAM dans sa machine virtuelle. Je ne vais pas reproduire ce code (il y a trop de lignes) et je vais me concentrer sur l'essentiel. Alors, le chat dans le 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 : nous réservons de la mémoire et y écrivons un gigaoctet. Que montre ce test ?
$ ./memtest
4.06504 Go/s
Environ 4 Go/s.
Quoi?!?
Comment?!?
C'est un Core i7 (même s'il n'est pas le plus récent), DDR4, le processeur est presque au repos — POURQUOI?!
La réponse, comme toujours, est extraordinairement ordinaire.
L'opérateur new (tout comme la fonction malloc, au fait) ne réserve en réalité pas de mémoire. Lors de cet appel, l'allocateur consulte la liste des blocs disponibles dans le pool de mémoire, et s'il n'y en a pas, il appelle sbrk() pour augmenter le segment de données, puis renvoie à l'application un lien vers l'adresse du nouveau bloc récemment alloué.
Le problème est que le bloc alloué est entièrement virtuel. Les pages de mémoire réelles ne sont pas réservées.
Et lorsque la première accès à chaque page de ce segment alloué se produit, le MMU déclenche une page d'erreur, après quoi une page réelle est assignée à la page virtuelle à laquelle on accède.
Ainsi, en réalité, nous testons non pas la performance du bus et des modules RAM, mais celle du MMU et du VMM du système d'exploitation. Et pour tester la véritable performance de la mémoire vive, nous devons simplement initialiser une fois les blocs réservés. 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;
}C'est-à-dire que nous initialisons simplement les tampons réservés avec une valeur par défaut (char 0).
Vérifions :
$ ./memtest
28.5714 Go/s
C'est autre chose.
Morale — si vous avez besoin de grands tampons pour travailler rapidement, n'oubliez pas de les initialiser.
Source : habr.com
