悪いテストについてもう少し詳しく

ある日、ユーザーが仮想マシンの 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;
}

それは簡単です - メモリを割り当て、そこに XNUMX GB を書き込みます。 そして、このテストは何を示しているのでしょうか?

$ ./memtest
4.06504 GB / sの

約4GB/秒。

何?!?!

どうやって?!?!?

これは Core i7 (最新ではありませんが)、DDR4、プロセッサーはほとんど搭載されていません - なぜですか??!

いつものように、答えは異常に普通です。

new 演算子 (ちなみに、malloc 関数など) は、実際にはメモリを割り当てません。 この呼び出しにより、アロケータはメモリ プール内の空き位置のリストを調べ、空きがない場合は sbrk() を呼び出してデータ セグメントを増やし、新しい位置からのアドレスへの参照をプログラムに返します。割り当てられました。

問題は、割り当てられた領域が完全に仮想であることです。 実メモリ ページは割り当てられません。

そして、この割り当てられたセグメントから各ページへの最初のアクセスが発生すると、MMU はページ フォールトを「発射」し、その後、アクセスされている仮想ページに実際のページが割り当てられます。

したがって、実際には、バスと RAM モジュールのパフォーマンスではなく、オペレーティング システムの MMU と VMM のパフォーマンスをテストしています。 RAM の実際のパフォーマンスをテストするには、割り当てられた領域を XNUMX 回初期化するだけで済みます。 たとえば次のようになります。

#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

コメントを追加します