Одного разу мені випадково потрапив на очі код, яким користувач намагався моніторити продуктивність 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