c++ - لماذا يمكن تحسين lambdas أفضل من قبل المجمع من وظائف عادي؟




optimization c++11 (2)

في كتابه The C++ Standard Library (Second Edition) نيكولاي جوسوتيس Nicolai Josuttis أن اللامداس يمكن تحسينه بشكل أفضل من قبل المترجم أكثر من الوظائف العادية.

بالإضافة إلى ذلك ، يعمل compilers C ++ على تحسين lambdas أفضل من قيامهم بوظائف عادية. (صفحة 213)

لماذا هذا؟

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


لأنه عندما تقوم بتمرير "دالة" إلى خوارزمية فأنت في الحقيقة تمر في مؤشر لتعمل لذا يجب أن تقوم بمكالمة غير مباشرة عبر المؤشر إلى الوظيفة. عند استخدام lambda تقوم بتمرير كائن إلى مثيل قالب تم إنشاؤه خصيصًا لهذا النوع ، وتكون المكالمة إلى وظيفة lambda عبارة عن مكالمة مباشرة ، وليس مكالمة عبر مؤشر دالة بحيث يمكن أن تكون مائلة بشكل أكبر.


والسبب هو أن lambdas هي كائنات وظيفية ، لذا فإن تمريرها إلى قالب وظيفي سيؤدي إلى إنشاء وظيفة جديدة خاصة بهذا الكائن. وبالتالي ، يستطيع المحول البرمجي توحيد مكالمة lambda.

من ناحية أخرى ، بالنسبة للوظائف ، ينطبق التحذير القديم: يتم تمرير مؤشر الوظيفة إلى قالب الوظيفة ، وعادةً ما يكون لدى المجمعين الكثير من المشكلات التي توجز المكالمات عبر مؤشرات الدالة. يمكن أن تكون مائلة نظريًا ، ولكن فقط إذا كانت الوظيفة المحيطة مائلة أيضًا.

كمثال ، ضع في الاعتبار قالب الدالة التالي:

template <typename Iter, typename F>
void map(Iter begin, Iter end, F f) {
    for (; begin != end; ++begin)
        *begin = f(*begin);
}

يطلق عليه مع امدا مثل هذا:

int a[] = { 1, 2, 3, 4 };
map(begin(a), end(a), [](int n) { return n * 2; });

نتائج في هذا إنشاء مثيل (تم إنشاؤه بواسطة المحول البرمجي):

template <>
void map<int*, _some_lambda_type>(int* begin, int* end, _some_lambda_type f) {
    for (; begin != end; ++begin)
        *begin = f.operator()(*begin);
}

... يعرف المحول البرمجي _some_lambda_type::operator () المكالمات به بشكل تافه. (وسيؤدي استدعاء map الوظائف مع أي lambda أخرى إلى إنشاء مثيل جديد map لأن لكل lambda نوع متميز.)

ولكن عند استدعاء مؤشر دالة ، تبدو عملية إنشاء المثيل كما يلي:

template <>
void map<int*, int (*)(int)>(int* begin, int* end, int (*f)(int)) {
    for (; begin != end; ++begin)
        *begin = f(*begin);
}

... و هنا يشير f إلى عنوان مختلف لكل استدعاء map ، وبالتالي لا يمكن للمبرمج أن يربط المكالمات بـ f ما لم يتم أيضًا تضمين المخططة المحيطة map بحيث يتمكن المترجم من حل f إلى وظيفة محددة واحدة.





compiler-optimization