Un poco más sobre pruebas incorrectas

Un día, accidentalmente capté la atención de un código que un usuario estaba tratando de monitorear el rendimiento de RAM en su máquina virtual. No daré este código (hay una "tela para los pies") y dejaré solo lo más esencial. Entonces, ¡el gato está 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/s\n";
	for (int i=0;i<CNT;i++) {
		delete buffers[i];
	}
	delete buffers;
	return 0;
}

Todo es simple: asignamos memoria y escribimos un gigabyte en él. ¿Y qué muestra esta prueba?

$ ./memtest
4.06504 GB / s


Aproximadamente 4GB / s.

¡¿¡¿Qué?!?!

¿¡¿¡¿Cómo?!?!?

Este es Core i7 (aunque no el más nuevo), DDR4, el procesador casi no está cargado - ¡¿POR QUÉ?!?!

La respuesta, como siempre, es inusualmente ordinaria.

El nuevo operador (como la función malloc, por cierto) en realidad no asigna memoria. Con esta llamada, el asignador mira la lista de secciones libres en el conjunto de memoria y, si no hay ninguna, llama a sbrk () para ampliar el segmento de datos y luego devuelve al programa un enlace a la dirección desde la sección recién asignada.

El problema es que el área seleccionada es completamente virtual. Las páginas de memoria real no están asignadas.

Y cuando se produce el primer acceso a cada página desde este segmento seleccionado, la MMU "dispara" la falla de la página, después de lo cual la página virtual a la que se realiza el acceso se asigna a la real.

Por lo tanto, de hecho, no estamos probando el rendimiento de los módulos de bus y RAM, sino el rendimiento de MMU y VMM del sistema operativo. Y para probar el rendimiento real de la RAM, solo necesitamos inicializar las secciones 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/s\n";
	for (int i=0;i<CNT;i++) {
		delete buffers[i];
	}
	delete buffers;
	return 0;
}

Es decir, simplemente inicializamos los búferes asignados con el valor predeterminado (char 0).

Comprobación:

$ ./memtest
28.5714 GB / s


Otra cosa.

Moraleja: si necesita memorias intermedias grandes para trabajar rápida y rápidamente, no olvide inicializarlas.

Source: https://habr.com/ru/post/undefined/


All Articles