Pewnego razu natknąłem się na kod, którym użytkownik próbował monitorować wydajność RAM w swojej maszynie wirtualnej. Nie będę przytaczał tego kodu (bo to „długi tekst”) i pozostawię tylko najważniejsze. A więc, kot w 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;
}Wszystko jest proste — przydzielamy pamięć i zapisujemy w niej jeden gigabajt. I co pokazuje ten test?
$ ./memtest
4.06504 GB/s
Około 4GB/s.
Co?!?!
Jak?!?!?
To Core i7 (choć nie najnowszy), DDR4, procesor prawie nieobciążony — DLACZEGO?!?!
Odpowiedź, jak zawsze, niezwykle zwyczajna.
Operator new (jak również funkcja malloc, swoją drogą) tak naprawdę nie przydziela pamięci. Przy tym wywołaniu alokator sprawdza listę wolnych bloków w puli pamięci, a jeśli ich nie ma, wywołuje sbrk(), aby zwiększyć segment danych, a potem zwraca programowi odwołanie do adresu z nowo przydzielonego bloku.
Problem w tym, że przydzielony blok jest w całości wirtualny. Rzeczywiste strony pamięci nie zostały przydzielone.
I kiedy następuje pierwsze odwołanie do każdej strony z tego przydzielonego segmentu, MMU „strzela” page fault, po czym wirtualnej stronie, do której następuje dostęp, przypisywana jest rzeczywista.
Dlatego tak naprawdę testujemy nie wydajność szyny i modułów RAM, a wydajność MMU i VMM systemu operacyjnego. A żeby testować rzeczywistą wydajność pamięci operacyjnej, musimy jednorazowo zainicjować przydzielone bloki. Na przykład tak:
#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 znaczy, po prostu inicjalizujemy przydzielane bufory wartością domyślną (char 0).
Sprawdzamy:
$ ./memtest
28.5714 GB/s
Inna sprawa.
Morał — jeśli potrzebujesz dużych buforów, aby pracować bardzo szybko, nie zapominaj ich inicjalizować.
Źródło: habr.com
