Încă puțin despre testarea incorectă

Odată, întâmplător, mi-a atras atenția un cod prin care un utilizator încerca să monitorizeze performanța RAM-ului în mașina sa virtuală. Nu voi prezenta acel cod (acolo era o „portiță”) și voi păstra doar esențialul. Așadar, pisica în 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;
}

Totul este simplu — alocăm memorie și scriem în ea un gigabyte. Și ce arată acest test?

$ ./memtest
4.06504 GB/s

Aproape 4GB/s.

Ce?!?!

Cum?!?!?

Acesta este un Core i7 (chiar dacă nu cel mai nou), DDR4, procesorul aproape că nu este încărcat — DE CE?!?!

Răspunsul, ca întotdeauna, este neobișnuit de obișnuit.

Operatorul new (la fel ca funcția malloc, de altfel) nu alocă de fapt memorie. La acest apel, alocatoarele verifică lista de segmente libere din pool-ul de memorie și, dacă nu sunt disponibile, callă sbrk() pentru a crește segmentul de date și apoi returnează programului un link către adresa din noul segment tocmai alocat.

Problema este că segmentul alocat este complet virtual. Pagini reale de memorie nu sunt alocate.

Și când are loc prima accesare a fiecărei pagini din acest segment alocat, MMU generează un page fault, după care paginii virtuale la care se accesează i se asociază o pagină reală.

De aceea, de fapt, testăm nu performanța magistralei și modulelor RAM, ci performanța MMU și VMM-ului sistemului de operare. Iar pentru a testa performanța reală a memoriei operaționale, trebuie doar să inițializăm o dată segmentele alocate. De exemplu, astfel:

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

Deci, pur și simplu inițializăm buffer-ele alocate cu o valoare implicită (char 0).

Verificăm:

$ ./memtest
28.5714 GB/s

Altceva.

Morala este — dacă aveți nevoie de buffere mari pentru a lucra rapid, nu uitați să le inițializați.

Sursa: habr.com

Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS 🔥 Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS | ProHoster