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

Веднъж случайно ми попаде код, с който потребителят се опитваше да следи производителността на 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