यह उत्तर मौजूदा जवाबों के सेट में योगदान करने के लिए है, जो मुझे लगता है कि std :: फ़ंक्शन कॉल की रनटाइम लागत के लिए अधिक सार्थक बेंचमार्क होना चाहिए।
एसटीडी :: फ़ंक्शन तंत्र को यह प्रदान करने के लिए पहचाना जाना चाहिए: किसी भी कॉल करने योग्य इकाई को स्टैड में बदला जा सकता है :: उपयुक्त हस्ताक्षर का कार्य। मान लें कि आपके पास एक लाइब्रेरी है जो z = f (x, y) द्वारा परिभाषित फ़ंक्शन पर एक सतह फिट बैठता है, तो आप इसे स्वीकार करने के लिए लिख सकते हैं std::function<double(double,double)>
, और लाइब्रेरी का उपयोगकर्ता आसानी से किसी भी कॉल करने योग्य इकाई को बदल सकता है; यह एक सामान्य कार्य हो सकता है, एक वर्ग उदाहरण की एक विधि, या लंबोदर, या कुछ भी जो std :: bind द्वारा समर्थित है।
टेम्पलेट दृष्टिकोण के विपरीत, यह अलग-अलग मामलों के लिए लाइब्रेरी फ़ंक्शन को फिर से जोड़ने के बिना काम करता है; तदनुसार, प्रत्येक अतिरिक्त मामले के लिए थोड़ा अतिरिक्त संकलित कोड की आवश्यकता होती है। ऐसा होना हमेशा संभव रहा है, लेकिन इसके लिए कुछ अजीब तंत्रों की आवश्यकता होती थी, और लाइब्रेरी के उपयोगकर्ता को इसे काम करने के लिए अपने फ़ंक्शन के चारों ओर एक एडाप्टर बनाने की आवश्यकता होगी। std :: फ़ंक्शन स्वचालित रूप से सभी मामलों के लिए एक सामान्य रनटाइम कॉल इंटरफ़ेस प्राप्त करने के लिए जो भी एडाप्टर की आवश्यकता होती है , का निर्माण करता है, जो एक नई और बहुत शक्तिशाली विशेषता है।
मेरे विचार से, यह std के लिए सबसे महत्वपूर्ण उपयोग मामला है: कार्य जहां तक प्रदर्शन का संबंध है: मैं एक कॉल करने की लागत में दिलचस्पी रखता हूं :: एक बार निर्माण होने के बाद कई बार कार्य करता है, और इसकी आवश्यकता है ऐसी स्थिति हो जहां कंपाइलर वास्तव में कहे जा रहे फ़ंक्शन को जानकर कॉल को ऑप्टिमाइज़ करने में असमर्थ हो (यानी आपको एक उचित बेंचमार्क प्राप्त करने के लिए किसी अन्य स्रोत फ़ाइल में कार्यान्वयन को छिपाने की आवश्यकता है)।
मैंने नीचे परीक्षण किया, ओपी के समान; लेकिन मुख्य परिवर्तन हैं:
- प्रत्येक मामले में 1 बिलियन बार लूप होता है, लेकिन एसटीडी :: फ़ंक्शन ऑब्जेक्ट केवल एक बार निर्मित होते हैं। मैंने आउटपुट कोड को देखकर पाया है कि वास्तविक std :: function कॉल का निर्माण करते समय 'ऑपरेटर नया' कहा जाता है (शायद तब जब वे अनुकूलित नहीं होते हैं)।
- अवांछित अनुकूलन को रोकने के लिए परीक्षण को दो फ़ाइलों में विभाजित किया गया है
- मेरे मामले हैं: (ए) फ़ंक्शन इनबिल्ड है (बी) फ़ंक्शन को एक साधारण फ़ंक्शन पॉइंटर द्वारा पास किया जाता है (सी) फ़ंक्शन एक संगत फ़ंक्शन है जिसे एसटीडी के रूप में लपेटा जाता है :: फ़ंक्शन (डी) फ़ंक्शन एक असंगत फ़ंक्शन है जो एसटीडी के साथ संगत बनाया गया है :: बाँध, एसटीडी के रूप में लिपटे :: समारोह
मुझे मिलने वाले परिणाम हैं:
केस (d) थोड़ा धीमा हो जाता है, लेकिन अंतर (लगभग 0.05 nsec) शोर में अवशोषित हो जाता है।
निष्कर्ष यह है कि std :: function तुलनीय ओवरहेड (कॉल टाइम पर) एक फ़ंक्शन पॉइंटर का उपयोग करने के लिए है, तब भी जब वास्तविक फ़ंक्शन के लिए सरल 'बाइंड' अनुकूलन हो। इनलाइन दूसरों की तुलना में 2 ns तेज है, लेकिन यह एक अपेक्षित ट्रेडऑफ है क्योंकि इनलाइन एकमात्र मामला है जो रन के समय में 'हार्ड-वायर्ड' है।
जब मैं एक ही मशीन पर johan-lundberg का कोड चलाता हूं, तो मुझे लगभग 39 nsec प्रति लूप दिखाई दे रहा है, लेकिन वहां के लूप में बहुत कुछ है, जिसमें std :: function का वास्तविक कंस्ट्रक्टर और डिस्ट्रॉक्टर भी शामिल है, जो संभवतः काफी अधिक है चूंकि इसमें एक नया और हटाना शामिल है।
-O2 gcc 4.8.1, x86_64 लक्ष्य (कोर i5) के लिए।
ध्यान दें, संकलक को दो फ़ाइलों में विभाजित किया गया है, ताकि संकलक को उन कार्यों के विस्तार से रोका जा सके जहां उन्हें बुलाया जाता है (एक मामले में जहां यह इरादा है) को छोड़कर।
----- पहला स्रोत फ़ाइल --------------
#include <functional>
// simple funct
float func_half( float x ) { return x * 0.5; }
// func we can bind
float mul_by( float x, float scale ) { return x * scale; }
//
// func to call another func a zillion times.
//
float test_stdfunc( std::function<float(float)> const & func, int nloops ) {
float x = 1.0;
float y = 0.0;
for(int i =0; i < nloops; i++ ){
y += x;
x = func(x);
}
return y;
}
// same thing with a function pointer
float test_funcptr( float (*func)(float), int nloops ) {
float x = 1.0;
float y = 0.0;
for(int i =0; i < nloops; i++ ){
y += x;
x = func(x);
}
return y;
}
// same thing with inline function
float test_inline( int nloops ) {
float x = 1.0;
float y = 0.0;
for(int i =0; i < nloops; i++ ){
y += x;
x = func_half(x);
}
return y;
}
----- दूसरा स्रोत फ़ाइल -------------
#include <iostream>
#include <functional>
#include <chrono>
extern float func_half( float x );
extern float mul_by( float x, float scale );
extern float test_inline( int nloops );
extern float test_stdfunc( std::function<float(float)> const & func, int nloops );
extern float test_funcptr( float (*func)(float), int nloops );
int main() {
using namespace std::chrono;
for(int icase = 0; icase < 4; icase ++ ){
const auto tp1 = system_clock::now();
float result;
switch( icase ){
case 0:
result = test_inline( 1e9);
break;
case 1:
result = test_funcptr( func_half, 1e9);
break;
case 2:
result = test_stdfunc( func_half, 1e9);
break;
case 3:
result = test_stdfunc( std::bind( mul_by, std::placeholders::_1, 0.5), 1e9);
break;
}
const auto tp2 = high_resolution_clock::now();
const auto d = duration_cast<milliseconds>(tp2 - tp1);
std::cout << d.count() << std::endl;
std::cout << result<< std::endl;
}
return 0;
}
रुचि रखने वालों के लिए, यहाँ एडेप्टर 'mul_by' बनाने के लिए बनाया गया है जो फ्लोट (फ्लोट) की तरह दिखता है - इसे 'कहा जाता है' जब बाइंड (mul_by, _1,0.5) के रूप में बनाया जाता है:
movq (%rdi), %rax ; get the std::func data
movsd 8(%rax), %xmm1 ; get the bound value (0.5)
movq (%rax), %rdx ; get the function to call (mul_by)
cvtpd2ps %xmm1, %xmm1 ; convert 0.5 to 0.5f
jmp *%rdx ; jump to the func
(इसलिए यह थोड़ा और तेज़ हो सकता है अगर मैं बाँध में 0.5f लिखा होता ...) ध्यान दें कि 'x' पैरामीटर% xmm0 में आता है और बस वहीं रहता है।
यहां उस क्षेत्र का कोड है जहां फ़ंक्शन का निर्माण किया जाता है, कॉल करने से पहले test_stdfunc - c ++ फ़ाइल के माध्यम से चलाएं:
movl $16, %edi
movq $0, 32(%rsp)
call operator new(unsigned long) ; get 16 bytes for std::function
movsd .LC0(%rip), %xmm1 ; get 0.5
leaq 16(%rsp), %rdi ; (1st parm to test_stdfunc)
movq mul_by(float, float), (%rax) ; store &mul_by in std::function
movl $1000000000, %esi ; (2nd parm to test_stdfunc)
movsd %xmm1, 8(%rax) ; store 0.5 in std::function
movq %rax, 16(%rsp) ; save ptr to allocated mem
;; the next two ops store pointers to generated code related to the std::function.
;; the first one points to the adaptor I showed above.
movq std::_Function_handler<float (float), std::_Bind<float (*(std::_Placeholder<1>, double))(float, float)> >::_M_invoke(std::_Any_data const&, float), 40(%rsp)
movq std::_Function_base::_Base_manager<std::_Bind<float (*(std::_Placeholder<1>, double))(float, float)> >::_M_manager(std::_Any_data&, std::_Any_data const&, std::_Manager_operation), 32(%rsp)
call test_stdfunc(std::function<float (float)> const&, int)
std::function
और केवल अगर आपको वास्तव में कॉल करने योग्य वस्तुओं के एक विषम संग्रह की आवश्यकता है (यानी रनटाइम में कोई और भेदभावपूर्ण जानकारी उपलब्ध नहीं है)।