Яшчэ крыху аб няправільным тэсціраванні

Аднойчы мне выпадкова патрапіў на вочы код, якім карыстач спрабаваў маніторыць прадукцыйнасць 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 Гб / с

Прыкладна 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 Гб / с

Іншая справа.

Мараль - калі вам патрэбныя вялікія буферы каб хутка-хутка працаваць, не забывайце іх ініцыялізаваць.

Крыніца: habr.com

Дадаць каментар