Una vez, me encontré accidentalmente con un código que un usuario estaba utilizando para monitorear el rendimiento de la RAM en su máquina virtual. No voy a mostrar ese código (es un «lío») y dejaré solo lo más relevante. ¡Así que, gato en el estudio!
#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 muy simple: asignamos memoria y escribimos un gigabyte en ella. ¿Y qué muestra esta prueba?
$ ./memtest
4.06504 GB/s
Aproximadamente 4GB/s.
¿Qué?!?!
¿Cómo?!?!?
Este es un Core i7 (aunque no el más nuevo), la DDR4, el procesador está casi sin carga — ¿POR QUÉ?!?!
La respuesta, como siempre, es extraordinariamente común.
El operador new (al igual que la función malloc, por cierto) en realidad no asigna memoria. En esta llamada, el asignador revisa la lista de bloques libres en el grupo de memoria, y si no hay ninguno, llama a sbrk() para aumentar el segmento de datos, y luego devuelve a la programa un enlace a la dirección del nuevo bloque que acaba de ser asignado.
El problema es que el bloque asignado es completamente virtual. Las páginas de memoria reales no se han asignado.
Y cuando se accede por primera vez a cada página de este segmento asignado, la MMU 'dispara' un page fault, después de lo cual se asigna a la página virtual que se está accediendo una página real.
Por lo tanto, en realidad no estamos probando el rendimiento del bus y de los módulos de RAM, sino el rendimiento de la MMU y de la VMM del sistema operativo. Y para probar el rendimiento real de la memoria RAM, simplemente necesitamos inicializar las áreas asignadas una vez. Por ejemplo así:
#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;
}Es decir, simplemente inicializamos los buffers asignados con un valor por defecto (char 0).
Verificando:
$ ./memtest
28.5714 GB/s
Eso es diferente.
La moraleja es: si necesitas grandes buffers para trabajar rápido, no olvides inicializarlos.
Fuente: habr.com
