كيفية مزامنة مكتبات C & C++ مع الحد الأدنى من عقوبة الأداء؟

StackOverflow https://stackoverflow.com/questions/286105

سؤال

لدي مكتبة C تحتوي على العديد من الإجراءات الرياضية للتعامل مع المتجهات والمصفوفات والكواتيرنيونات وما إلى ذلك.يجب أن يبقى في لغة C لأنني غالبًا ما أستخدمه للعمل المضمن وكملحق Lua.بالإضافة إلى ذلك، لدي مغلفات فئة C++ للسماح بإدارة أكثر ملاءمة للكائنات والتحميل الزائد للمشغل لعمليات الرياضيات باستخدام C API.يتكون الغلاف فقط من ملف رأس ويتم استخدام أكبر قدر ممكن من التضمين.

هل هناك عقوبة ملحوظة لتغليف كود C مقابل النقل وإدراج التنفيذ مباشرة في فئة C++؟يتم استخدام هذه المكتبة في التطبيقات الحرجة للوقت.إذن، هل يعوض التعزيز الناتج عن القضاء على عدم التوجيه عناء صيانة المنفذين؟

مثال على واجهة C:

typedef float VECTOR3[3];

void v3_add(VECTOR3 *out, VECTOR3 lhs, VECTOR3 rhs);

مثال على غلاف C++:

class Vector3
{
private:
    VECTOR3 v_;

public:
    // copy constructors, etc...

    Vector3& operator+=(const Vector3& rhs)
    {
        v3_add(&this->v_, this->v_, const_cast<VECTOR3> (rhs.v_));
        return *this;
    }

    Vector3 operator+(const Vector3& rhs) const
    {
        Vector3 tmp(*this);
        tmp += rhs;
        return tmp;
    }

    // more methods...
};
هل كانت مفيدة؟

المحلول

سيتم تضمين غلافك نفسه، لكن عادةً لن يتم تضمين استدعاءات الأسلوب الخاصة بك إلى مكتبة C.(قد يتطلب هذا تحسينات وقت الارتباط وهي ممكنة من الناحية الفنية، ولكن بالنسبة لـ AFAIK بدائية في أحسن الأحوال في أدوات اليوم)

بشكل عام، استدعاء الدالة في حد ذاته ليس مكلفًا للغاية.لقد انخفضت تكلفة الدورة بشكل كبير خلال السنوات الماضية، ويمكن التنبؤ بها بسهولة، وبالتالي فإن عقوبة الاستدعاء في حد ذاتها لا تذكر.

ومع ذلك، فإن التضمين يفتح الباب لمزيد من التحسينات:إذا كان لديك v = a + b + c، فإن فئة الغلاف الخاصة بك تفرض إنشاء متغيرات المكدس، بينما بالنسبة للمكالمات المضمنة، يمكن الاحتفاظ بمعظم البيانات في مكدس FPU.كما يسمح الكود المضمن بتبسيط التعليمات، مع الأخذ في الاعتبار القيم الثابتة، والمزيد.

لذلك بينما قياس قبل أن تستثمر القاعدة صحيحة، أتوقع بعض المجال للتحسينات هنا.


الحل النموذجي هو تحويل تنفيذ لغة C إلى تنسيق يمكن استخدامه إما كوظائف مضمنة أو كنص "C":

// V3impl.inl
void V3DECL v3_add(VECTOR3 *out, VECTOR3 lhs, VECTOR3 rhs)
{
    // here you maintain the actual implementations
    // ...
}

// C header
#define V3DECL 
void V3DECL v3_add(VECTOR3 *out, VECTOR3 lhs, VECTOR3 rhs);

// C body
#include "V3impl.inl"


// CPP Header
#define V3DECL inline
namespace v3core {
  #include "V3impl.inl"
} // namespace

class Vector3D { ... }

من المحتمل أن يكون هذا منطقيًا فقط بالنسبة لطرق مختارة ذات أجسام بسيطة نسبيًا.سأقوم بنقل الأساليب إلى مساحة اسم منفصلة لتطبيق C++، حيث أنك لن تحتاج إليها بشكل مباشر عادةً.

(لاحظ أن المضمّن هو مجرد تلميح للمترجم، ولا يفرض تضمين الطريقة.لكن هذا جيد:إذا تجاوز حجم رمز الحلقة الداخلية ذاكرة التخزين المؤقت للتعليمات، فإن التضمين يضر بالأداء بسهولة)

ما إذا كان يمكن حل الممر/المرجع على حدة يعتمد على قوة المترجم الخاص بك ، فقد رأيت العديد من المتغيرات FOO (x * out) ، في حين أن x foo () تحافظ على القيم في السجلات.

نصائح أخرى

إذا كنت مجرد التفاف المكالمات مكتبة C في C ++ وظائف فئة (وبعبارة أخرى، فإن وظائف C ++ لا تفعل شيئا ولكن ظائف الاتصال C)، ثم المترجم سوف تحسين هذه الدعوات بحيث انها ليست ركلة جزاء الأداء.

وكما هو الحال مع أي سؤال حول الأداء، سوف يقال لك لقياس للحصول على الإجابة (وهذا هو الجواب الصحيح بدقة).

ولكن كقاعدة عامة من الإبهام، لطرق مضمنة البسيطة التي يمكن فعلا أن inlined، سترى أي عقوبة الأداء. بشكل عام، طريقة مضمنة أن يفعل شيئا سوى تمرير الدعوة إلى وظيفة أخرى هو مرشح كبير لرمز مصدر.

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

وهذا النوع من الشيء هو حول آخر شيء لتكون مهتما. القلق الأول عن صنع الشفرة صحيحة، للصيانة، والذي تستخدمه الخوارزميات المناسبة.

وكما جرت العادة مع كل ما يتعلق بالتحسين، فإن الإجابة هي أنه يتعين عليك قياس الأداء نفسه قبل أن تعرف ما إذا كان التحسين جديرًا بالاهتمام أم لا.

  • قم بقياس وظيفتين مختلفتين، إحداهما تستدعي وظائف النمط C مباشرة والأخرى تستدعي من خلال الغلاف.تعرف على أيهما يعمل بشكل أسرع، أو إذا كان الفرق ضمن هامش الخطأ في قياسك (مما يعني أنه لا يوجد فرق يمكنك قياسه).
  • انظر إلى رمز التجميع الذي تم إنشاؤه بواسطة الوظيفتين في الخطوة السابقة (في دول مجلس التعاون الخليجي، استخدم -S أو -save-temps).تحقق مما إذا كان المترجم قد فعل شيئًا غبيًا، أو إذا كانت الأغلفة الخاصة بك تحتوي على أي خطأ في الأداء.

ما لم يكن فرق الأداء كبيرًا جدًا لصالح عدم استخدام الغلاف، فإن إعادة التنفيذ ليست فكرة جيدة، لأنك تخاطر بإدخال أخطاء (والتي قد تؤدي أيضًا إلى نتائج تبدو منطقية ولكنها خاطئة).حتى لو كان الفرق كبيرًا، سيكون من الأسهل والأقل خطورة أن تتذكر فقط أن لغة C++ متوافقة جدًا مع لغة C وأن تستخدم مكتبتك في نمط C حتى داخل كود C++.

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

وأنا الترميز لDS وعدد قليل من أجهزة ARM الأخرى ونقطة عائمة أشرار ... واضطررت الى typedef وتطفو الى FixedPoint <16،8>

إذا كنت قلقا من أن النفقات العامة من استدعاء وظائف يتباطأ لكم بانخفاض، لماذا لا اختبار رمز مصدر رمز C أو تحويله إلى وحدات الماكرو؟

وأيضا، لماذا لا يحسن من صحة CONST من قانون C بينما كنت في ذلك - يجب حقا const_cast أن تستخدم لماما، وخاصة على واجهات يمكنك التحكم

.
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top