تنفيذ مزدوج للتعليمة البرمجية للتحويل (C++)
التنفيذ المزدوج للتعليمة البرمجية للتحويل يشير إلى فقدان الأداء الذى قد تواجهه عندما يقوم استدعاء دالة فى السياق المدار باستدعاء دالة Visual C++ مدارة و حيث يستدعى تنفيذ البرنامج نقطة الإدخال الأصلية للدالة من اجل استدعاء الدالة المدارة. يناقش هذا الموضوع أين يحدث التنفيذ المزدوج للتعليمة البرمجية للتحويل و كيف يمكنك تجنبه لتحسين الأداء.
ملاحظات
بشكل افتراضي، عند التحويل البرمجي بواسطة /clr (لا /clr:pure) ، يتسبب تعريف الدالة المدارة فى أن برنامج التحويل البرمجي ينشئ نقطة إدخال مدارة و نقطة إدخال أصلية. يسمح هذا للدالات المدارة أن يتم استدعاؤها من مواقع الاستدعاء الأصلية و المدارة. و على كل حال، عند وجود نقطة إدخال أصلية، يمكن أن تكون نقطة الإدخال لكافة الاستدعاءات للدالات. إذا تمت إدارة استدعاءات الدالات, ستقوم نقطة الإدخال الأصلية باستدعاء نقطة الإدخال المدارة. فى الحقيقة, يطلب استدعاءان لاستدعاء الدالة (ومن ثم تنفيذ مزدوج للتعليمة البرمجية للتحويل ). على سبيل المثال، الدالات الظاهرية يتم استدعاؤها دوماً خلال نقطة الإدخال الأصلية.
أحد الحلول هى إعلام برنامج التحويل البرمجي بألا يقوم بإنشاء نقطة إدخال أصلية للدالة المدارة، إذاً الدالة سيتم استدعاؤها فقط من سياق مدار باستخدام اصطلاح الاستدعاء __clrcall .
وبالمثل، إذا قمت بتصدير ( dllexport, dllimport) دالة مدارة ، يتم إنشاء نقطة إدخال أصلية، و أي دالة تستورد و تستدعى تلك الدالة سيتقوم بالاستدعاء عبر نقطة الإدخال الأصلية. لتجنب التنفيذ المزدوج للتعليمة البرمجية للتحويل فى هذه الحالة ،لا تستخدم دلالات أصلية للتصدير/الاستيراد ; ببساطة قم بالإشارة إلى بيانات التعريف عبر #using (راجع The #using Directive).
في Visual C++ 2005 تم تحديث المحول البرمجي لتقليل التنفيذ المزدوج للتعليمة البرمجية غير الضرورى. على سبيل المثال، أي دالة ذات نوع مدار في التوقيع (بما في ذلك نوع الإرجاع) سيتم ضمنيًا وسمها بـ __clrcall. للحصول على مزيد من المعلومات حول إزالة التنفيذ المزدوج للتعليمة البرمجية, راجع https://msdn.microsoft.com/msdnmag/issues/05/01/COptimizations/default.aspx .
المثال
الوصف
يوضح النموذج التالي التنفيذ المزدوج للتعليمة البرمجية للتحويل. عند التحويل البرمجي الأصلي (بدون /clr) ، الاستدعاء للدالة الظاهرية في main يقوم بإنشاء استدعاء واحد لنسخة منشئ T و استدعاء واحد إلى المدمر. نتوصل لسلوك مماثل عند التصريح بالدالة الظاهرية بواسطة /clr و __clrcall. ومع ذلك، ما أن يتم التحويل البرمجى بواسطة /clr، يقوم استدعاء الدالة بإنشاء استدعاء لمنشئ النسخة ولكن يوجد استدعاء آخر لمنشئ النسخة بسبب التعليمة البرمجية للتحويل من أصلي إلى مدار.
الرمز
// double_thunking.cpp
// compile with: /clr
#include <stdio.h>
struct T {
T() {
puts(__FUNCSIG__);
}
T(const T&) {
puts(__FUNCSIG__);
}
~T() {
puts(__FUNCSIG__);
}
T& operator=(const T&) {
puts(__FUNCSIG__);
return *this;
}
};
struct S {
virtual void /* __clrcall */ f(T t) {};
} s;
int main() {
S* pS = &s;
T t;
printf("calling struct S\n");
pS->f(t);
printf("after calling struct S\n");
}
عينة مخرجات:
__thiscall T::T(void)
calling struct S
__thiscall T::T(const struct T &)
__thiscall T::T(const struct T &)
__thiscall T::~T(void)
__thiscall T::~T(void)
after calling struct S
__thiscall T::~T(void)
المثال
الوصف
العينة السابقة توضّح وجود تنفيذ المزدوج للتعليمة البرمجية للتحويل. تبين هذه العينة تأثيره. تقوم حلقة for باستدعاء الدالة الظاهرية و يقوم البرنامج بالإبلاغ عن وقت التنفيذ. يتم الإبلاغ عن الوقت الأبطأ عند ترجمة البرنامج برمجياً بواسطة /clr. يتم الإبلاغ عن الأوقات الأسرع عند الترجمة بدون /clr أو إذا كانت الدالة الظاهرية مصرح بها باستخدام __clrcall.
الرمز
// double_thunking_2.cpp
// compile with: /clr
#include <time.h>
#include <stdio.h>
#pragma unmanaged
struct T {
T() {}
T(const T&) {}
~T() {}
T& operator=(const T&) { return *this; }
};
struct S {
virtual void /* __clrcall */ f(T t) {};
} s;
int main() {
S* pS = &s;
T t;
clock_t start, finish;
double duration;
start = clock();
for ( int i = 0 ; i < 1000000 ; i++ )
pS->f(t);
finish = clock();
duration = (double)(finish - start) / (CLOCKS_PER_SEC);
printf( "%2.1f seconds\n", duration );
printf("after calling struct S\n");
}
عينة مخرجات:
4.2 seconds
after calling struct S