ทำไมคอมไพเลอร์สามารถปรับแต่งแลมบ์ดาได้ดีกว่าฟังก์ชั่นธรรมดา?


171

ในหนังสือของเขาThe C++ Standard Library (Second Edition)Nicolai Josuttis ระบุว่า lambdas สามารถปรับให้เหมาะสมโดยคอมไพเลอร์มากกว่าฟังก์ชั่นธรรมดา

นอกจากนี้คอมไพเลอร์ C ++ เพิ่มประสิทธิภาพ lambdas ได้ดีกว่าฟังก์ชั่นทั่วไป (หน้า 213)

ทำไมถึงเป็นอย่างนั้น?

ฉันคิดว่าเมื่อพูดถึงการอินไลน์ก็ไม่น่าจะมีความแตกต่างอีกต่อไป เหตุผลเดียวที่ฉันคิดได้ก็คือคอมไพเลอร์อาจมีบริบทท้องถิ่นที่ดีขึ้นกับลูกแกะและสิ่งเหล่านี้สามารถทำให้สมมติฐานมากขึ้นและดำเนินการเพิ่มประสิทธิภาพมากขึ้น


ที่เกี่ยวข้อง
iammilind

โดยทั่วไปคำสั่งจะใช้กับวัตถุฟังก์ชันทั้งหมดไม่ใช่แค่ lambdas
newacct

4
นั่นจะไม่ถูกต้องเนื่องจากพอยน์เตอร์ของฟังก์ชั่นเป็นวัตถุของฟังก์ชันด้วย
Johannes Schaub - litb

2
@litb: ฉันคิดว่าฉันไม่เห็นด้วย ^ W ^ W ^ W ^ W ^ W ^ W (หลังจากดูมาตรฐาน) ฉันไม่ทราบว่า C ++ ism ถึงแม้ว่าฉันคิดในภาษาทั่วไป (และตาม วิกิพีเดีย) คนหมายถึงอินสแตนซ์ของบางคนเรียกคลาสเมื่อพวกเขาพูดว่าวัตถุฟังก์ชั่น
Sebastian Mach

1
คอมไพเลอร์บางตัวสามารถปรับแต่ง lambdas ได้ดีกว่าฟังก์ชั่นธรรมดาแต่ไม่ใช่ทั้งหมด :-(
Cody Grey

คำตอบ:


175

เหตุผลก็คือ lambdas เป็นวัตถุของฟังก์ชั่นดังนั้นการส่งผ่านไปยังเทมเพลตของฟังก์ชันจะยกตัวอย่างฟังก์ชันใหม่สำหรับวัตถุนั้นโดยเฉพาะ คอมไพเลอร์สามารถ inline โทรแลมบ์เล็กน้อย

สำหรับฟังก์ชั่นในทางกลับกันข้อแม้เก่าใช้: ตัวชี้ฟังก์ชั่นได้รับการส่งผ่านไปยังแม่แบบฟังก์ชั่นและคอมไพเลอร์แบบดั้งเดิมมีปัญหามากมายในการโทรผ่านตัวชี้ฟังก์ชั่น พวกมันสามารถอนุมานตามทฤษฎีได้ แต่ถ้าฟังก์ชันรอบข้างถูกอินไลน์เช่นกัน

เป็นตัวอย่างพิจารณาแม่แบบฟังก์ชั่นต่อไปนี้:

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ด้วยใด ๆแลมบ์ดาอื่น ๆ ที่จะสร้างการเริ่มใหม่mapเนื่องจากแต่ละแลมบ์ดามีประเภทที่แตกต่างกัน.)

แต่เมื่อเรียกด้วยตัวชี้ฟังก์ชันการสร้างอินสแตนซ์จะมีลักษณะดังนี้:

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เป็นฟังก์ชันหนึ่งได้


4
บางทีอาจเป็นเรื่องที่ควรค่าแก่การกล่าวถึงว่าการสร้างแม่แบบฟังก์ชั่นเดียวกันด้วยการแสดงออกแลมบ์ดาที่แตกต่างกันจะสร้างฟังก์ชั่นใหม่ทั้งหมดด้วยประเภทที่ไม่เหมือนใครซึ่งอาจเป็นข้อเสียเปรียบ
เย็น

2
@greggo อย่างแน่นอน ปัญหาคือเมื่อจัดการฟังก์ชั่นที่ไม่สามารถ inline (เพราะพวกเขามีขนาดใหญ่เกินไป) ที่นี่การเรียกไปยังการเรียกกลับยังคงอยู่ในกรณีของแลมบ์ดา แต่ไม่ได้อยู่ในกรณีของตัวชี้ฟังก์ชั่น std::sortเป็นตัวอย่างคลาสสิกของการใช้ lambdas แทนที่จะเป็นตัวชี้ฟังก์ชั่นที่นี่จะเพิ่มขึ้นเจ็ดเท่า (อาจมากกว่า แต่ฉันไม่มีข้อมูลเกี่ยวกับสิ่งนั้น!) เพิ่มประสิทธิภาพ
Konrad Rudolph

1
@greggo คุณสับสนสองฟังก์ชันที่นี่: อันที่เรากำลังส่งแลมบ์ดาไป (เช่นstd::sortหรือmapในตัวอย่างของฉัน) และแลมบ์ดาเอง แลมบ์ดามักจะมีขนาดเล็ก ฟังก์ชั่นอื่น ๆ - ไม่จำเป็นต้อง เรากังวลเกี่ยวกับการเรียกอินแลมบ์ดาแลมด้าในฟังก์ชั่นอื่น
Konrad Rudolph

2
@ greggo ฉันรู้ นี่คือสิ่งที่ประโยคสุดท้ายของคำตอบของฉันพูดแม้ว่า
Konrad Rudolph

1
สิ่งที่ฉันคิดว่าอยากรู้อยากเห็น (มีเพียงสะดุดเมื่อ) มันเป็นฟังก์ชั่นบูลีนที่เรียบง่ายpredที่มีความหมายที่มองเห็นได้และการใช้ gcc v5.3 std::find_if(b, e, pred)ไม่ได้อยู่ในบรรทัดpredแต่std::find_if(b, e, [](int x){return pred(x);})ทำ เสียงดังดังขึ้นทั้งสองอย่าง แต่ไม่สร้างโค้ดเร็วเท่า g ++ ด้วยแลมบ์ดา
rici

26

เพราะเมื่อคุณผ่าน "ฟังก์ชั่น" ไปยังอัลกอริทึมคุณจะผ่านตัวชี้ไปยังฟังก์ชั่นดังนั้นจึงต้องทำการโทรทางอ้อมผ่านตัวชี้ไปยังฟังก์ชั่น เมื่อคุณใช้แลมบ์ดาคุณกำลังส่งวัตถุไปยังอินสแตนซ์เทมเพลตที่อินสแตนซ์เป็นพิเศษสำหรับประเภทนั้นและการเรียกฟังก์ชั่นแลมบ์ดาเป็นการโทรโดยตรงไม่ใช่การโทรผ่านตัวชี้ฟังก์ชันเพื่อให้มีแนวโน้มมากขึ้น


5
"การเรียกฟังก์ชั่นแลมบ์ดาเป็นการโทรโดยตรง" - แน่นอน และสิ่งเดียวกันนั้นเป็นจริงสำหรับทุกฟังก์ชั่นวัตถุไม่ใช่แค่ lambdas มันเป็นแค่พอยน์เตอร์ของฟังก์ชั่นที่ไม่สามารถอินไลน์ได้อย่างง่ายดายหากเลย
Pete Becker
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.