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

ذات يوم ، لفتت الأنظار عن غير قصد رمزًا كان مستخدمًا يحاول مراقبة أداء ذاكرة الوصول العشوائي في جهازه الافتراضي. لن أعطي هذا الرمز (هناك "وسادة قدم") وأترك ​​الأساسي فقط. لذا ، القطة في الاستوديو!

#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/s\n";
	for (int i=0;i<CNT;i++) {
		delete buffers[i];
	}
	delete buffers;
	return 0;
}

كل شيء بسيط - نخصص ذاكرة ونكتب غيغابايت واحد فيها. وماذا يظهر هذا الاختبار؟

$ ./memtest
4.06504 جيجابايت / ثانية


تقريبًا 4 جيجابايت / ثانية.

ماذا؟!؟!

كيف؟!؟!؟

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

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

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

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

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

لذلك ، في الواقع ، نحن لا نختبر أداء وحدات الناقل وذاكرة الوصول العشوائي ، ولكن أداء وحدة 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/s\n";
	for (int i=0;i<CNT;i++) {
		delete buffers[i];
	}
	delete buffers;
	return 0;
}

أي أننا ببساطة نقوم بتهيئة المخازن المؤقتة المخصصة بالقيمة الافتراضية (char 0).

التحقق:

$ ./memtest
28.5714 جيجابايت / ثانية


شيء آخر.

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

Source: https://habr.com/ru/post/undefined/


All Articles