المزيد عن الاختبار السيئ

في أحد الأيام، صادفت بالصدفة رمزًا يشير إلى أن أحد المستخدمين كان يحاول مراقبة أداء ذاكرة الوصول العشوائي (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 جيجابايت / ثانية

حوالي 4 جيجابايت/ثانية.

ماذا؟!؟!

كيف؟!؟!؟

هذا هو Core i7 (وإن لم يكن الأحدث)، DDR4، المعالج لا يتم تحميله تقريبًا - لماذا؟!؟!

الجواب، كما هو الحال دائما، عادي على نحو غير عادي.

المشغل الجديد (بالمناسبة، مثل وظيفة malloc) لا يخصص الذاكرة فعليًا. باستخدام هذا الاستدعاء، ينظر المُخصص إلى قائمة المواقع المجانية في تجمع الذاكرة، وإذا لم يكن هناك أي منها، يستدعي sbrk() لزيادة شريحة البيانات، ثم يُرجع إلى البرنامج مرجعًا إلى العنوان من الموقع الجديد فقط المخصصة.

المشكلة هي أن المنطقة المخصصة افتراضية بالكامل. لم يتم تخصيص أي صفحات ذاكرة حقيقية.

وعندما يحدث الوصول الأول إلى كل صفحة من هذا الجزء المخصص، تقوم الوحدة MMU "بإطلاق النار" على خطأ في الصفحة، وبعد ذلك يتم تعيين صفحة حقيقية للصفحة الافتراضية التي يتم الوصول إليها.

لذلك، في الواقع، نحن لا نختبر أداء وحدات الناقل وذاكرة الوصول العشوائي (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 جيجابايت / ثانية

شيء آخر.

العبرة من القصة - إذا كنت بحاجة إلى مخازن مؤقتة كبيرة للعمل بسرعة، فلا تنس تهيئتها.

المصدر: www.habr.com

إضافة تعليق