Още малко за неправилното тестване

Веднъж, случайно ми попадна код, с който потребителят се опитваше да наблюдава производителността на RAM в своята виртуална машина. Няма да цитирам кода (там е „поръчка“) и ще оставя само най-съществени неща. И така, котка в студиото!

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

Всичко е просто – заделяме памет и записваме в нея един гигабайт. И какво показва този тест?

$ ./memtest
4.06504 GB/s

Приблизително 4GB/s.

Как?!?!

Как?!?!?

Това е Core i7 (да, не е най-новият), DDR4, процесорът почти не е натоварен – ЗАЩО?!?!

Отговорът, както винаги, е необикновено обикновен.

Операторът new (както и функцията malloc, всъщност) всъщност не заделя памет. При това извикване алокаторът разглежда списъка със свободни блокове в пула с памет и, ако няма такива, извиква sbrk(), за да увеличи сегмента с данни, след което връща на програмата препратка към адреса от новозаделения блок.

Проблемът е, че заделеният блок е изцяло виртуален. Реалните страници памет не са заделени.

И когато се случи първото обращение към всяка страница от този заделен сегмент, MMU „изстрелва“ page fault, след което на виртуалната страница, до която има достъп, се назначава реална.

Затова на практика тестваме не производителността на шината и модулите RAM, а производителността на MMU и VMM на операционната система. А за да тестваме реалната производителност на оперативната памет, просто трябва да инициализираме веднъж заделените участъци. Например така:

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

Така че просто инициализираме заделените буфери със стойността по подразбиране (char 0).

Проверяваме:

$ ./memtest
28.5714 GB/s

Съвсем друго.

Моралът е - ако ви трябват големи буфери, за да работите бързо-скоро, не забравяйте да ги инициализирате.

Източник: habr.com

Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри 🔥 Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри | ProHoster