Once, I happened to see a piece of code that a user was trying to use to monitor RAM performance in their virtual machine. I won’t provide the code (it’s quite a mess) but will leave only the essential part. So, here comes the point!
#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;
}It's simple — we allocate memory and write one gigabyte to it. And what does this test show?
$ ./memtest
4.06504 GB/s
Approximately 4GB/s.
What?!?!
How?!?!?
This is a Core i7 (though not the newest), DDR4, the processor is hardly loaded — WHY?!?!
The answer, as always, is extraordinarily ordinary.
The new operator (as well as the malloc function, by the way) does not actually allocate memory. During this call, the allocator checks the list of free blocks in the memory pool, and if there are none, it calls sbrk() to increase the data segment, and then returns a reference to the address from the newly allocated block.
The problem is that the allocated block is entirely virtual. Real memory pages are not allocated.
And when the first access to each page from this allocated segment occurs, the MMU "fires" a page fault, after which a real page is assigned to the virtual page being accessed.
Therefore, we are not actually testing the performance of the bus and RAM modules, but rather the performance of the MMU and VMM of the operating system. To test the real performance of RAM, we simply need to initialize the allocated blocks once. For example:
#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;
}That is, we simply initialize the allocated buffers to a default value (char 0).
Checking:
$ ./memtest
28.5714 GB/s
Now that’s something.
The moral of the story — if you need large buffers to work quickly, don't forget to initialize them.
Source: habr.com
