คำตอบนี้มีวัตถุประสงค์เพื่อสนับสนุนชุดคำตอบที่มีอยู่สิ่งที่ฉันเชื่อว่าเป็นเกณฑ์มาตรฐานที่มีความหมายมากขึ้นสำหรับต้นทุนรันไทม์ของ std :: function call
ควรรับรู้กลไกของฟังก์ชัน std :: สำหรับสิ่งที่มีให้: เอนทิตี callable ใด ๆ สามารถแปลงเป็นฟังก์ชัน std :: ของลายเซ็นที่เหมาะสม สมมติว่าคุณมีไลบรารีที่เหมาะกับพื้นผิวกับฟังก์ชันที่กำหนดโดย z = f (x, y) คุณสามารถเขียนมันเพื่อยอมรับ a std::function<double(double,double)>
และผู้ใช้ไลบรารีสามารถแปลงเอนทิตี callable ใด ๆ ได้อย่างง่ายดาย ไม่ว่าจะเป็นฟังก์ชั่นธรรมดาวิธีการของอินสแตนซ์ของชั้นเรียนหรือแลมบ์ดาหรืออะไรก็ตามที่ได้รับการสนับสนุนโดย std :: bind
แตกต่างจากวิธีการของเทมเพลตการทำงานนี้โดยไม่ต้องคอมไพล์ฟังก์ชันไลบรารีอีกครั้งสำหรับกรณีที่แตกต่างกัน ดังนั้นจึงจำเป็นต้องมีการคอมไพล์โค้ดพิเศษเล็กน้อยสำหรับแต่ละเคสเพิ่มเติม มีความเป็นไปได้ที่จะทำให้เกิดขึ้นเสมอ แต่มันต้องใช้กลไกที่น่าอึดอัดใจและผู้ใช้ไลบรารีอาจต้องสร้างอะแดปเตอร์รอบฟังก์ชันเพื่อให้ทำงานได้ std :: function สร้างสิ่งที่อะแดปเตอร์จำเป็นโดยอัตโนมัติเพื่อให้ได้ทั่วไปอินเทอร์เฟซการเรียกรันไทม์สำหรับทุกกรณีซึ่งเป็นคุณสมบัติใหม่และทรงพลังมาก
ในมุมมองของฉันนี่เป็นกรณีการใช้งานที่สำคัญที่สุดสำหรับ std :: function เท่าที่เกี่ยวข้องกับประสิทธิภาพ: ฉันสนใจค่าใช้จ่ายในการเรียกใช้ std :: function หลายครั้งหลังจากที่มันถูกสร้างขึ้นครั้งเดียวและจำเป็นต้อง เป็นสถานการณ์ที่คอมไพเลอร์ไม่สามารถเพิ่มประสิทธิภาพการโทรโดยรู้ว่าฟังก์ชั่นจริงถูกเรียก (เช่นคุณต้องซ่อนการใช้งานในไฟล์ต้นฉบับอื่นเพื่อให้ได้มาตรฐานที่เหมาะสม)
ฉันทำแบบทดสอบด้านล่างคล้ายกับของ OP แต่การเปลี่ยนแปลงที่สำคัญคือ:
- แต่ละเคสวน 1 พันล้านครั้ง แต่วัตถุฟังก์ชัน std :: ถูกสร้างเพียงครั้งเดียว ฉันพบโดยดูที่รหัสผลลัพธ์ที่เรียกว่า 'ตัวดำเนินการใหม่' เมื่อสร้าง std :: function calls จริง (อาจไม่ใช่เมื่อพวกมันถูกปรับให้เหมาะสม)
- การทดสอบแบ่งออกเป็นสองไฟล์เพื่อป้องกันการเพิ่มประสิทธิภาพที่ไม่พึงประสงค์
- กรณีของฉันคือ: (a) ฟังก์ชั่น inlined (b) ฟังก์ชั่นผ่านตัวชี้ฟังก์ชั่นธรรมดา (c) ฟังก์ชั่นเป็นฟังก์ชั่นที่เข้ากันได้ห่อเป็น std :: function (d) ฟังก์ชั่น ผูกห่อเป็นมาตรฐาน :: ฟังก์ชั่น
ผลลัพธ์ที่ฉันได้รับคือ:
กรณี (d) มีแนวโน้มที่จะช้าลงเล็กน้อย แต่ความแตกต่าง (ประมาณ 0.05 nsec) ถูกดูดซับไว้ในเสียง
สรุปก็คือฟังก์ชั่น std :: นั้นเปรียบได้กับค่าใช้จ่าย (เมื่อเวลาโทร) ถึงการใช้ตัวชี้ฟังก์ชั่นแม้ในขณะที่มีการปรับ 'ผูก' ที่ง่ายกับฟังก์ชั่นที่เกิดขึ้นจริง อินไลน์คือ 2 ns เร็วกว่าที่อื่น ๆ แต่นั่นคือการแลกเปลี่ยนที่คาดหวังเนื่องจากอินไลน์เป็นกรณีเดียวซึ่งเป็น 'สายแข็ง' ในเวลาทำงาน
เมื่อฉันรันโค้ดของ johan-lundberg บนเครื่องเดียวกันฉันเห็นประมาณ 39 nsec ต่อลูป แต่มีจำนวนมากในลูปที่นั่นรวมถึงตัวสร้างจริงและ destructor ของฟังก์ชัน std :: ซึ่งอาจค่อนข้างสูง เพราะมันเกี่ยวข้องกับการใหม่และลบ
-O2 gcc 4.8.1, ถึง x86_64 เป้าหมาย (core 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' ดูเหมือนลอย (float) - นี่คือ 'เรียกว่า' เมื่อฟังก์ชั่นที่สร้างขึ้นเป็นผูก (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 ++ filt:
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
ถ้าหากคุณต้องการคอลเลกชันที่แตกต่างกันของวัตถุที่เรียกได้เท่านั้น (เช่นไม่มีข้อมูลการแบ่งแยกที่สามารถใช้งานได้ในขณะใช้งาน)