Ještě něco o nesprávném testování

Jednou jsem náhodou narazil na kód, kterým se uživatel snažil monitorovat výkon RAM ve své virtuální machine. Tento kód uvádět nebudu (bylo by to nepřehledné) a uvedu pouze to nejpodstatnější. Tak tedy, kočka ve studiu!

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

Je to jednoduché — alokujeme paměť a zapíšeme do ní jeden gigabajt. A co tento test ukazuje?

$ ./memtest
4.06504 GB/s

Přibližně 4GB/s.

Co?!?!

Jak?!?!?

Toto je Core i7 (i když ne ten nejnovější), DDR4, procesor téměř není zatížen — PROČ?!?!

Odpověď je, jak to bývá, neobyčejně obyčejná.

Operátor new (stejně jako funkce malloc, mimochodem) ve skutečnosti nealokuje paměť. Při tomto volání alokátor prochází seznam volných bloků v paměťovém poolu, a pokud žádné nejsou, zavolá sbrk(), aby zvýšil segment dat, a poté vrátí programu odkaz na adresu z právě přiděleného bloku.

Problém je v tom, že přidělený blok je zcela virtuální. Skutečné paměťové stránky nejsou přiděleny.

A když dojde k prvnímu přístupu k každé stránce z tohoto přiděleného segmentu, MMU "vystřelí" page fault, po kterém je virtuální stránce, ke které je přistupováno, přiřazena reálná.

Proto ve skutečnosti netestuji výkon sběrnice a modulů RAM, ale výkon MMU a VMM operačního systému. A abychom otestovali skutečný výkon operační paměti, potřebujeme jen jednou inicializovat přidělené bloky. Například takto:

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

Tedy, jednoduše inicializujeme přidělované buffery výchozí hodnotou (char 0).

Kontrolujeme:

$ ./memtest
28.5714 GB/s

To je jiná věc.

Morální ponaučení — pokud potřebujete velké buffery pro rychlou práci, nezapomeňte je inicializovat.

Zdroj: habr.com

Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster