เหตุใดโปรแกรมจึงใช้สแตกการโทรหากมีการเรียกใช้ฟังก์ชันซ้อนกัน?


32

ทำไมคอมไพเลอร์ถึงไม่มีโปรแกรมเช่นนี้:

function a(b) { return b^2 };
function c(b) { return a(b) + 5 };

และแปลงเป็นโปรแกรมเช่นนี้:

function c(b) { return b^2 + 5 };

ด้วยเหตุนี้จึงไม่จำเป็นต้องให้คอมพิวเตอร์จำที่อยู่ส่งคืนของ c (b)

ฉันคิดว่าพื้นที่ฮาร์ดดิสก์ที่เพิ่มขึ้นและ RAM ที่จำเป็นในการจัดเก็บโปรแกรมและสนับสนุนการรวบรวม (ตามลำดับ) คือเหตุผลที่เราใช้ call stack ถูกต้องไหม


30
ดูว่าเกิดอะไรขึ้นถ้าคุณทำสิ่งนี้ในโปรแกรมที่มีความหมายทุกขนาด โดยเฉพาะอย่างยิ่งฟังก์ชั่นที่ถูกเรียกจากมากกว่าหนึ่งสถานที่
253751

10
นอกจากนี้บางครั้งคอมไพเลอร์ไม่ทราบว่าฟังก์ชั่นใดถูกเรียก! ตัวอย่างที่ไร้สาระ:window[prompt("Enter function name","")]()
253751

26
คุณจะใช้งานfunction(a)b { if(b>0) return a(b-1); }อย่างไร
pjc50

8
ความสัมพันธ์กับการเขียนโปรแกรมฟังก์ชั่นอยู่ที่ไหน
mastov

14
@ pjc50: มัน recursive bหางดังนั้นเรียบเรียงแปลห่วงที่มีไม่แน่นอน แต่ประเด็นที่ได้รับไม่ใช่ฟังก์ชั่นการเรียกซ้ำทั้งหมดที่สามารถกำจัดการเรียกซ้ำได้และแม้กระทั่งเมื่อฟังก์ชั่นสามารถทำได้ด้วยหลักการคอมไพเลอร์อาจไม่ฉลาดพอที่จะทำเช่นนั้น
Steve Jessop

คำตอบ:


75

สิ่งนี้เรียกว่า "อินไลน์" และคอมไพเลอร์หลายตัวทำเช่นนี้เป็นกลยุทธ์การปรับให้เหมาะสมในกรณีที่เหมาะสม

ในตัวอย่างของคุณการเพิ่มประสิทธิภาพนี้จะประหยัดทั้งพื้นที่และเวลาในการดำเนินการ แต่ถ้ามีการเรียกใช้ฟังก์ชันในหลาย ๆ ที่ในโปรแกรม (ไม่ใช่เรื่องแปลก!) มันจะเพิ่มขนาดรหัสดังนั้นกลยุทธ์จึงน่าสงสัยมากขึ้น (และแน่นอนถ้าฟังก์ชั่นเรียกตัวเองไม่ว่าทางตรงหรือทางอ้อมมันจะเป็นไปไม่ได้ที่จะอินไลน์เนื่องจากโค้ดจะมีขนาดไม่ จำกัด )

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


7
@Blrfl: คอมไพเลอร์สมัยใหม่ไม่ต้องการคำจำกัดความในส่วนหัวอีกต่อไป พวกเขาสามารถอินไลน์ข้ามหน่วยการแปล แม้ว่าจะต้องมีตัวเชื่อมโยงที่ดี คำจำกัดความในไฟล์ส่วนหัวเป็นวิธีแก้ปัญหาสำหรับ linkers ใบ้
MSalters

3
"ฟังก์ชั่นที่เปิดรับผู้โทรจากภายนอกไม่สามารถปรับให้เหมาะสมได้" - ต้องมีฟังก์ชั่นนี้ แต่จะมีไซต์โทรใด ๆ ก็ตาม (ในรหัสของคุณเองหรือหากมีแหล่งที่มา
Random832

14
ว้าว 28 upvotes สำหรับคำตอบที่ไม่ได้พูดถึงเหตุผลที่การฝังทุกสิ่งเป็นไปไม่ได้: การเรียกซ้ำ
mastov

3
@R .. : LTO เป็นการเพิ่มประสิทธิภาพเวลาลิงค์ไม่ใช่การเพิ่มประสิทธิภาพเวลาโหลด
MSalters

2
@immibis: แต่ถ้ามีการแนะนำ stack อย่างชัดเจนโดยคอมไพเลอร์ stack นั้นจะเป็น call stack
user2357112 รองรับ Monica

51

คำถามของคุณมีสองส่วน: เหตุใดจึงมีฟังก์ชั่นหลายฟังก์ชั่น (แทนที่จะแทนที่การเรียกใช้ฟังก์ชันด้วยคำจำกัดความ) และทำไมจึงใช้ฟังก์ชั่นเหล่านั้นด้วย call stack แทนการจัดสรรข้อมูลแบบสแตติก

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

แล้วก็มีปัญหาสำหรับไลบรารี่: คุณไม่รู้หรอกว่าฟังก์ชั่นไหนจะถูกเรียกจากที่ไหนและบ่อยแค่ไหนดังนั้น "ไลบรารี่" จึงไม่สามารถคอมไพล์ได้จริงๆส่งไปยังไคลเอนต์ทั้งหมดในรูปแบบระดับสูงที่สะดวกสบาย inlined ลงในแอปพลิเคชัน นอกเหนือจากปัญหาอื่น ๆ ด้วยสิ่งนี้คุณสูญเสียการเชื่อมโยงแบบไดนามิกที่มีประโยชน์ทั้งหมด

นอกจากนี้ยังมีหลายเหตุผลที่ไม่ฟังก์ชั่นอินไลน์แม้ว่าคุณจะสามารถ:

  1. มันไม่จำเป็นต้องเร็วขึ้น การตั้งค่ากรอบสแต็กและการฉีกมันอาจเป็นคำแนะนำรอบเดียวแบบโหลสำหรับฟังก์ชั่นที่มีขนาดใหญ่หรือลูปที่ไม่ถึง 0.1% ของเวลาดำเนินการ
  2. มันอาจจะช้ากว่า การทำสำเนาของรหัสมีค่าใช้จ่ายเช่นมันจะสร้างแรงกดดันมากขึ้นในแคชคำสั่ง
  3. ฟังก์ชั่นบางอย่างมีขนาดใหญ่มากและถูกเรียกจากหลาย ๆ ที่การฝังมันไว้ทุกหนทุกแห่งจะเพิ่มไบนารี่ได้มากกว่าสิ่งที่สมเหตุสมผล
  4. คอมไพเลอร์มักจะมีปัญหากับฟังก์ชั่นที่มีขนาดใหญ่มาก ทุกอย่างอื่นเท่ากันฟังก์ชั่นขนาด 2 * N ใช้เวลามากกว่า 2 * T ซึ่งฟังก์ชั่นขนาด N ใช้เวลา T

1
ฉันประหลาดใจกับประเด็นที่ 4 เหตุผลนี้คืออะไร
JacquesB

12
@JacquesB อัลกอริธึมการเพิ่มประสิทธิภาพจำนวนมากเป็นกำลังสอง, ลูกบาศก์, หรือแม้กระทั่งในทางเทคนิค NP-complete ตัวอย่างที่ยอมรับได้คือการจัดสรรการลงทะเบียนซึ่งเป็น NP-complete โดยการเปรียบเทียบกับการระบายสีกราฟ (โดยทั่วไปแล้วคอมไพเลอร์จะไม่พยายามแก้ปัญหาที่แน่นอน แต่มีเพียง heuristic ที่แย่มากสองตัวเท่านั้นที่ทำงานในเวลาเชิงเส้น) การเพิ่มประสิทธิภาพแบบ one-pass ที่เรียบง่ายจำนวนมากจำเป็นต้องมีการวิเคราะห์แบบ superlinear ผ่านก่อนเช่นทุกอย่างที่ขึ้นอยู่กับการปกครอง n บันทึกเวลา n กับบล็อกพื้นฐาน n)

2
"คุณมีสองคำถามที่นี่จริง ๆ " ไม่ฉันไม่ เพียงหนึ่ง - ทำไมไม่ปฏิบัติกับการเรียกใช้ฟังก์ชั่นเป็นเพียงตัวยึดตำแหน่งที่คอมไพเลอร์อาจพูดแทนด้วยรหัสของฟังก์ชันที่เรียกว่า?
moonman239

4
@ moonman239 ดังนั้นถ้อยคำของคุณก็ทำให้ฉันผิดหวัง ถึงกระนั้นคำถามของคุณสามารถย่อยสลายได้เหมือนที่ฉันทำในคำตอบและฉันคิดว่านั่นเป็นมุมมองที่มีประโยชน์

16

สแต็คช่วยให้เราสามารถข้ามข้อ จำกัด ที่กำหนดโดยจำนวน จำกัด ที่ลงทะเบียนไว้อย่างสวยงาม

ลองนึกภาพว่ามี "การลงทะเบียน az" ถึง 26 globals (หรือแม้แต่มีการลงทะเบียนขนาด 7 ไบต์ของชิป 8080 เท่านั้น) และทุกฟังก์ชั่นที่คุณเขียนในแอพนี้จะแชร์รายการแบบแบนนี้

การเริ่มต้นที่ไร้เดียงสาคือการจัดสรรรีจิสเตอร์สองสามตัวแรกให้กับฟังก์ชั่นแรกและรู้ว่ามันใช้เวลาเพียง 3 เริ่มต้นด้วย "d" สำหรับฟังก์ชั่นที่สอง ... คุณหมดเร็ว

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

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

(การเรียกซ้ำเป็นสิ่งที่ยอดเยี่ยม แต่ถ้าคุณต้องลงทะเบียนโดยไม่ต้องกองซ้อนคุณจะต้องขอบคุณกองซ้อนจริงๆ )


ฉันคิดว่าพื้นที่ฮาร์ดดิสก์ที่เพิ่มขึ้นและ RAM ที่จำเป็นในการจัดเก็บโปรแกรมและสนับสนุนการรวบรวม (ตามลำดับ) คือเหตุผลที่เราใช้ call stack ถูกต้องไหม

ในขณะที่เราสามารถอินไลน์วันนี้ได้มากขึ้น ("ความเร็วมากขึ้น" ดีเสมอ; "การชุมนุมน้อยกว่ากิโลไบต์" หมายถึงน้อยมากในโลกของการสตรีมวิดีโอ) ข้อ จำกัด หลักคือความสามารถของคอมไพเลอร์

ตัวอย่างเช่นวัตถุ polymorphic - หากคุณไม่รู้จักวัตถุชนิดเดียวและชนิดเดียวที่คุณจะได้รับคุณจะไม่สามารถแบนได้ คุณต้องดูที่คุณสมบัติ vtable ของวัตถุและเรียกผ่านตัวชี้นั้น ... ไม่สำคัญที่จะทำที่รันไทม์ไม่สามารถอินไลน์ในเวลาคอมไพล์ได้

Toolchain ที่ทันสมัยสามารถ inline ฟังก์ชันที่กำหนด polymorphically อย่างมีความสุขเมื่อมันแบนราบพอที่ผู้เรียก (s) จะรู้ว่ารสชาติของ obj คืออะไร:

class Base {
    public: void act() = 0;
};
class Child1: public Base {
    public: void act() {};
};
void ActOn(Base* something) {
    something->act();
}
void InlineMe() {
    Child1 thingamabob;
    ActOn(&thingamabob);
}

ในข้างต้นคอมไพเลอร์สามารถเลือกที่จะรักษา inlining แบบคงที่จาก InlineMe ผ่านสิ่งที่อยู่ภายใน () หรือไม่จำเป็นต้องสัมผัส vtables ใด ๆ ที่รันไทม์

แต่ความไม่แน่นอนใด ๆ ในสิ่งที่รสชาติของวัตถุจะปล่อยให้มันเป็นฟังก์ชั่นที่ไม่ต่อเนื่องแม้ว่าการเรียกอื่น ๆ ของฟังก์ชั่นเดียวกันจะถูกแทรก


11

กรณีที่วิธีการนั้นไม่สามารถจัดการ:

function fib(a) { if(a>2) return fib(a-1)+fib(a-2); else return 1; }

function many(a) { for(i = 1 to a) { b(i); };}

มีมีภาษาและแพลตฟอร์มที่มีการ จำกัด หรือไม่มีสแต็คโทร ไมโคร PIC มีสแต็คฮาร์ดแวร์ จำกัด เฉพาะระหว่าง 2 และ 32 รายการ สิ่งนี้จะสร้างข้อ จำกัด ในการออกแบบ

การห้ามเรียกซ้ำ COBOL: https://stackoverflow.com/questions/27806812/in-cobol-is-it-possible-to-recursively-call-a-paragraph

การกำหนดห้ามการเรียกซ้ำหมายถึงคุณสามารถเป็นตัวแทนของ callgraph ทั้งหมดของโปรแกรมในรูปแบบ DAG คอมไพเลอร์ของคุณสามารถเปล่งฟังก์ชั่นหนึ่งชุดสำหรับแต่ละสถานที่ที่มันถูกเรียกด้วยการกระโดดคงที่แทนที่จะกลับมา ไม่จำเป็นต้องใช้สแต็กพื้นที่โปรแกรมมากขึ้นอาจจะค่อนข้างมากสำหรับระบบที่ซับซ้อน แต่สำหรับระบบฝังตัวเล็ก ๆ นั่นหมายความว่าคุณสามารถรับประกันได้ว่าจะไม่มีกองล้นที่รันไทม์ซึ่งเป็นข่าวร้ายสำหรับการควบคุมเครื่องปฏิกรณ์นิวเคลียร์ / กังหันเทอร์โบ / คันเร่งรถยนต์ ฯลฯ


12
ตัวอย่างแรกของคุณคือการสอบถามซ้ำขั้นพื้นฐานและคุณแก้ไขแล้ว แต่ตัวอย่างที่สองของคุณดูเหมือนจะเป็นการวนซ้ำเพื่อเรียกใช้ฟังก์ชันอื่น ฟังก์ชั่นอินไลน์มีความแตกต่างจากการคลี่วง ฟังก์ชั่นสามารถอยู่ในแถวโดยไม่ต้องคลี่วง หรือฉันไม่ได้รับรายละเอียดที่ลึกซึ้ง?
jpmc26

1
หากตัวอย่างแรกของคุณมีความหมายเพื่อกำหนดซีรีส์ Fibonacci มันผิด (ไม่ได้รับfibสาย)
Paŭlo Ebermann

1
ในขณะที่การห้ามการสอบถามซ้ำนั้นหมายความว่ากราฟการโทรทั้งหมดสามารถแสดงเป็น DAG แต่ไม่ได้หมายความว่าจะสามารถแสดงรายการลำดับการโทรที่ซ้อนทั้งหมดในพื้นที่ที่สมเหตุสมผล ในโครงการหนึ่งของฉันสำหรับไมโครคอนโทรลเลอร์ที่มีพื้นที่โค้ด 128KB ฉันทำผิดพลาดในการขอกราฟการโทรซึ่งรวมถึงฟังก์ชั่นทั้งหมดที่อาจส่งผลต่อความต้องการพารามิเตอร์ - RAM สูงสุดและกราฟการโทรนั้นเกินขีด จำกัด กราฟการโทรที่สมบูรณ์จะยิ่งยาวขึ้นไปอีกและนั่นก็เป็นโปรแกรมที่เหมาะกับพื้นที่โค้ด 128K
supercat

8

คุณต้องการฟังก์ชั่นอินไลน์และคอมไพเลอร์( ปรับให้เหมาะสม ) ส่วนใหญ่กำลังทำเช่นนั้น

ขอให้สังเกตว่าการ inlining ต้องการฟังก์ชันที่เรียกว่าเป็นที่รู้จัก (และมีประสิทธิภาพเฉพาะในกรณีที่ฟังก์ชั่นที่เรียกว่าไม่ใหญ่เกินไป) เนื่องจากแนวความคิดมันจะแทนการโทรโดยการเขียนใหม่ของฟังก์ชั่นที่เรียกว่า ดังนั้นคุณมักไม่สามารถอินไลน์ฟังก์ชั่นที่ไม่รู้จัก (เช่นตัวชี้ฟังก์ชันและอื่นที่มีฟังก์ชั่นจากการเชื่อมโยงแบบไดนามิก ห้องสมุดสาธารณะ - ซึ่งอาจมองเห็นเป็นวิธีการที่เสมือนจริงในบางvtableแต่คอมไพเลอร์บางคนอาจจะบางครั้งเพิ่มประสิทธิภาพผ่านdevirtualizationเทคนิค) แน่นอนว่ามันเป็นไปไม่ได้เสมอไปที่ฟังก์ชั่นแบบเรียกซ้ำ (คอมไพเลอร์ที่ฉลาดบางคนอาจใช้การประเมินบางส่วนและในบางกรณีสามารถที่จะฟังก์ชั่นแบบเรียกซ้ำแบบอินไลน์)

การสังเกตด้วยว่าการอินไลน์แม้จะเป็นไปได้ง่าย แต่ก็ไม่ได้ผลเสมอไป: คุณ (คอมไพเลอร์ของคุณ) สามารถเพิ่มขนาดรหัสที่แคช CPU (หรือตัวทำนายสาขา ) ทำงานได้อย่างมีประสิทธิภาพน้อยลงและทำให้โปรแกรมของคุณทำงาน ช้าลง

ฉันมุ่งเน้นไปที่สไตล์การเขียนโปรแกรมที่ใช้งานได้เล็กน้อยเนื่องจากคุณติดแท็กคำถามของคุณเป็นเช่นนี้

โปรดสังเกตว่าคุณไม่จำเป็นต้องมีcall stackใด ๆ (อย่างน้อยในแง่ของเครื่องของนิพจน์ "call stack") คุณสามารถใช้เฉพาะกอง

ดังนั้นดูที่การต่อเนื่องและอ่านเพิ่มเติมเกี่ยวกับรูปแบบการส่งต่อ (CPS) และการแปลง CPS (อย่างสังหรณ์ใจคุณสามารถใช้การปิดการต่อเนื่องเป็น "เฟรมการโทร" ที่จัดสรรใหม่ในฮีปและแบ่งประเภทของการเลียนแบบ ถ้าอย่างนั้นคุณต้องมีตัวรวบรวมขยะที่มีประสิทธิภาพ)

Andrew Appel เขียนหนังสือที่คอมไพล์ด้วย Continuationsและการรวบรวมขยะกระดาษเก่าสามารถทำได้เร็วกว่าการจัดสรรสแต็ก ดูกระดาษของ A.Kennedy (ICFP2007) การคอมไพล์ด้วย Continuations, ต่อ

ฉันยังแนะนำให้อ่านหนังสือLisp In Small Piecesของ Queinnec ซึ่งมีหลายบทที่เกี่ยวข้องกับความต่อเนื่องและการรวบรวม

โปรดสังเกตว่าบางภาษา (เช่นBrainfuck ) หรือเครื่องที่เป็นนามธรรม (เช่นOISC , RAM ) ไม่มีสิ่งอำนวยความสะดวกการโทรใด ๆ แต่ยังคงเป็นทัวริงที่สมบูรณ์ดังนั้นคุณจึงไม่จำเป็นต้องมีกลไกในการเรียกฟังก์ชั่น มันสะดวกมาก BTW สถาปัตยกรรมชุดคำสั่งเก่าบางตัว(เช่นIBM / 370 ) ไม่มีแม้แต่ call call stack หรือคำสั่ง push machine call (IBM / 370 มีเพียงคำสั่งBranch และ Link machine)

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


2
เป็นที่ถกเถียงกันมากว่า CPS ไม่มี "call stack" มันไม่ได้อยู่ในสแต็กขอบเขตที่ลึกลับของ RAM ปกติที่มีการสนับสนุนฮาร์ดแวร์เล็กน้อยผ่าน%espฯลฯ แต่ก็ยังคงการทำบัญชีที่เทียบเท่ากับสปาเก็ตตี้ที่ตั้งชื่ออย่างเหมาะสมในพื้นที่อื่นของ RAM โดยเฉพาะอย่างยิ่งที่อยู่ผู้ส่งจะถูกเข้ารหัสเป็นหลักในความต่อเนื่อง และแน่นอนว่าการต่อเนื่องจะไม่เร็วกว่า (และสำหรับฉันแล้วนี่คือสิ่งที่ OP ได้รับ) มากกว่าการไม่โทรเลยผ่านอินไลน์

เอกสารเก่าของ Appel อ้างว่า (และแสดงด้วยการเปรียบเทียบ) ที่ CPS สามารถทำได้เร็วเท่ากับการมี call stack
Basile Starynkevitch

ฉันสงสัยอย่างนั้น แต่ไม่ว่าจะไม่ใช่สิ่งที่ฉันอ้างสิทธิ์

1
อันที่จริงนี่เป็นช่วงปลายยุค 1980 MIPS เวิร์กสเตชัน ลำดับชั้นแคชบนพีซีปัจจุบันอาจทำให้ประสิทธิภาพแตกต่างกันเล็กน้อย มีการวิเคราะห์เอกสารหลายเรียกร้อง Appel (และแน่นอนในเครื่องปัจจุบันกองจัดสรรอาจจะเล็กน้อยเร็วขึ้น -by percents- ไม่กี่กว่าระมัดระวังเก็บขยะ)
Basile Starynkevitch

1
@Gilles: แกน ARM รุ่นใหม่จำนวนมากเช่น Cortex M0 และ M3 (และอื่น ๆ เช่น M4) ได้รับการสนับสนุนฮาร์ดแวร์สแต็คสำหรับสิ่งต่าง ๆ เช่นการจัดการขัดจังหวะ เพิ่มเติมชุดคำสั่ง Thumb ประกอบด้วยชุดย่อยที่ จำกัด ของคำสั่ง STRM / STRM ที่รวม STRMDB R13 กับการรวมกันของ R0-R7 ใด ๆ ที่มี / ไม่มี LR และ LDRMIA R13 ของการรวมกันของ R0-R7 ใด ๆ ที่มี / ไม่มี PC R13 เป็นตัวชี้สแต็ก
supercat

8

Inlining (การแทนที่การเรียกใช้ฟังก์ชันด้วยฟังก์ชันที่เทียบเท่า) ทำงานได้เป็นกลยุทธ์การเพิ่มประสิทธิภาพสำหรับฟังก์ชั่นที่เรียบง่ายขนาดเล็ก โอเวอร์เฮดของการเรียกฟังก์ชั่นสามารถซื้อขายได้อย่างมีประสิทธิภาพสำหรับการลงโทษเล็กน้อยในขนาดโปรแกรมเพิ่มเติม (หรือในบางกรณีไม่มีการลงโทษเลย)

อย่างไรก็ตามฟังก์ชั่นขนาดใหญ่ซึ่งจะเรียกฟังก์ชั่นอื่น ๆ อาจนำไปสู่การระเบิดอย่างมากในขนาดของโปรแกรมหากทุกอย่างถูกแทรก

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

สำหรับสิ่งที่คุ้มค่าคุณสามารถใช้ฟังก์ชั่น callable ได้โดยไม่ต้อง call stack ตัวอย่างเช่น: IBM System / 360 เมื่อการเขียนโปรแกรมในภาษาเช่น FORTRAN บนฮาร์ดแวร์นั้นตัวนับโปรแกรม (ที่อยู่ผู้ส่ง) จะถูกบันทึกลงในส่วนเล็ก ๆ ของหน่วยความจำที่สงวนไว้ข้างหน้าจุดเข้าใช้งานของฟังก์ชัน อนุญาตให้ใช้ฟังก์ชันที่ใช้ซ้ำได้ แต่ไม่อนุญาตให้เรียกซ้ำหรือรหัสแบบมัลติเธรด (ความพยายามในการเรียกซ้ำหรือผู้เรียกซ้ำจะส่งผลให้ที่อยู่ผู้ส่งคืนที่บันทึกไว้ก่อนหน้านี้ถูกเขียนทับ)

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

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