Ještě trochu o špatném testování

Jednoho dne jsem náhodou narazil na kód, kterým se uživatel pokoušel sledovat výkon RAM ve svém virtuálním počítači. Tento kód neposkytnu (je tam „nánožník“) a nechám jen to nejnutnější. Takže kočka je 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é – přidělte paměť a zapište do ní jeden gigabajt. A co tento test ukazuje?

$ ./memtest
4.06504 GB / s

Přibližně 4 GB/s.

Co?!?!

Jak?!?!?

Jedná se o Core i7 (i když ne nejnovější), DDR4, procesor téměř nezatížený - PROČ?!?!

Odpověď je jako vždy neobvykle obyčejná.

Operátor new (mimochodem jako funkce malloc) ve skutečnosti paměť nepřiděluje. Při tomto volání se alokátor podívá na seznam volných míst ve fondu paměti, a pokud tam žádná nejsou, zavolá sbrk() ke zvětšení datového segmentu a poté vrátí programu odkaz na adresu z nového umístění právě přidělené.

Problém je, že přidělená oblast je zcela virtuální. Nejsou přiděleny žádné stránky skutečné paměti.

A když dojde k prvnímu přístupu ke každé stránce z tohoto přiděleného segmentu, MMU „vystřelí“ chybu stránky, po které je virtuální stránce, ke které se přistupuje, přiřazena skutečná stránka.

Ve skutečnosti tedy netestujeme výkon sběrnice a modulů RAM, ale výkon MMU a VMM operačního systému. A abychom mohli otestovat skutečný výkon RAM, stačí jednou inicializovat přidělené oblasti. 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;
}

To znamená, že jednoduše inicializujeme přidělené buffery s výchozí hodnotou (char 0).

Zkontrolujeme:

$ ./memtest
28.5714 GB / s

Další věc.

Morálka příběhu – pokud potřebujete velké buffery k rychlé práci, nezapomeňte je inicializovat.

Zdroj: www.habr.com

Přidat komentář