คำถามเดิม
เหตุใดหนึ่งลูปจึงช้ากว่าสองลูปมาก
สรุป:
กรณีที่ 1เป็นปัญหาการแก้ไขแบบคลาสสิกที่เกิดขึ้นอย่างไม่มีประสิทธิภาพ ฉันยังคิดว่านี่เป็นหนึ่งในเหตุผลสำคัญที่ทำให้สถาปัตยกรรมเครื่องและนักพัฒนาจำนวนมากสิ้นสุดลงด้วยการสร้างและออกแบบระบบมัลติคอร์ที่มีความสามารถในการทำแอพพลิเคชั่นแบบมัลติเธรดรวมถึงการเขียนโปรแกรมแบบขนาน
ดูจากวิธีการประเภทนี้โดยไม่ต้องเกี่ยวข้องกับวิธีการทำงานของฮาร์ดแวร์ระบบปฏิบัติการและคอมไพเลอร์เพื่อทำการจัดสรรฮีปที่เกี่ยวข้องกับการทำงานกับ RAM, แคช, ไฟล์เพจ ฯลฯ คณิตศาสตร์ที่เป็นรากฐานของอัลกอริธึมเหล่านี้แสดงให้เราเห็นว่าในสองสิ่งนี้เป็นทางออกที่ดีกว่า
เราสามารถใช้การเปรียบเทียบสิ่งมีBoss
ชีวิตSummation
ที่จะเป็นตัวแทนของสิ่งFor Loop
ที่ต้องเดินทางระหว่างคนงานA
&B
และ
เราสามารถเห็นได้อย่างง่ายดายว่ากรณีที่ 2นั้นเร็วกว่าครึ่งอย่างน้อยครึ่งถ้าไม่น้อยไปกว่ากรณีที่ 1เนื่องจากความแตกต่างของระยะทางที่จำเป็นสำหรับการเดินทางและเวลาที่ใช้ระหว่างคนงาน คณิตศาสตร์นี้จัดเรียงเกือบทั้งหมดและสมบูรณ์แบบด้วยทั้ง BenchMark Times และจำนวนความแตกต่างในคำแนะนำการประกอบ
ตอนนี้ฉันจะเริ่มอธิบายวิธีการทำงานด้านล่างทั้งหมด
การประเมินปัญหา
รหัสของ OP:
const int n=100000;
for(int j=0;j<n;j++){
a1[j] += b1[j];
c1[j] += d1[j];
}
และ
for(int j=0;j<n;j++){
a1[j] += b1[j];
}
for(int j=0;j<n;j++){
c1[j] += d1[j];
}
การพิจารณา
พิจารณาคำถามดั้งเดิมของ OP เกี่ยวกับตัวแปร 2 ตัวสำหรับลูปและคำถามที่แก้ไขเพิ่มเติมของเขาที่มีต่อพฤติกรรมของแคชพร้อมกับคำตอบที่ยอดเยี่ยมอื่น ๆ อีกมากมายและข้อคิดเห็นที่เป็นประโยชน์ ฉันต้องการลองทำสิ่งที่แตกต่างที่นี่โดยใช้แนวทางที่แตกต่างเกี่ยวกับสถานการณ์และปัญหานี้
วิธีการ
เมื่อพิจารณาทั้งสองลูปและการอภิปรายทั้งหมดเกี่ยวกับแคชและการยื่นหน้าฉันต้องการใช้วิธีอื่นในการมองสิ่งนี้จากมุมมองที่แตกต่างกัน สิ่งหนึ่งที่ไม่เกี่ยวข้องกับแคชและไฟล์เพจหรือการประมวลผลเพื่อจัดสรรหน่วยความจำอันที่จริงแล้ววิธีการนี้ไม่ได้เกี่ยวข้องกับฮาร์ดแวร์หรือซอฟต์แวร์จริงเลยแม้แต่น้อย
มุมมอง
หลังจากดูรหัสในขณะที่มันค่อนข้างชัดเจนว่าปัญหาคืออะไรและสิ่งที่สร้างมัน ลองแยกปัญหานี้เป็นปัญหาอัลกอริทึมและดูจากมุมมองของการใช้สัญลักษณ์ทางคณิตศาสตร์จากนั้นนำการเปรียบเทียบไปใช้กับปัญหาทางคณิตศาสตร์เช่นเดียวกับอัลกอริทึม
สิ่งที่เรารู้
เรารู้ว่าวงนี้จะวิ่ง 100,000 ครั้ง เราก็รู้เช่นกันa1
, b1
, c1
และd1
มีคำแนะนำเกี่ยวกับสถาปัตยกรรม 64 บิต ภายใน C ++ บนเครื่อง 32 บิตพอยน์เตอร์ทั้งหมดคือ 4 ไบต์และบนเครื่อง 64 บิตจะมีขนาด 8 ไบต์เนื่องจากพอยน์เตอร์มีความยาวคงที่
เรารู้ว่าเรามี 32 ไบต์ที่จะจัดสรรในทั้งสองกรณี ความแตกต่างเพียงอย่างเดียวคือเรากำลังจัดสรร 32 ไบต์หรือ 2 ชุด 2-8bytes ในการทำซ้ำแต่ละครั้งโดยกรณีที่ 2 เราจัดสรร 16 ไบต์สำหรับการทำซ้ำแต่ละครั้งสำหรับลูปอิสระทั้งสอง
ลูปทั้งสองยังคงเท่ากับ 32 ไบต์ในการจัดสรรทั้งหมด ด้วยข้อมูลนี้เราจะไปข้างหน้าและแสดงคณิตศาสตร์ทั่วไปอัลกอริทึมและการเปรียบเทียบแนวคิดเหล่านี้
เรารู้จำนวนครั้งที่ชุดหรือกลุ่มการทำงานเดียวกันที่จะต้องดำเนินการในทั้งสองกรณี เรารู้จำนวนหน่วยความจำที่ต้องจัดสรรในทั้งสองกรณี เราสามารถประเมินได้ว่าปริมาณงานโดยรวมของการจัดสรรระหว่างทั้งสองกรณีจะเท่ากัน
สิ่งที่เราไม่รู้
เราไม่ทราบว่าจะใช้เวลานานเท่าใดในแต่ละกรณีเว้นแต่ว่าเราตั้งตัวนับและทำการทดสอบเกณฑ์มาตรฐาน อย่างไรก็ตามการวัดประสิทธิภาพได้ถูกรวมไว้แล้วจากคำถามเดิมและจากคำตอบและความคิดเห็นบางส่วนเช่นกัน และเราสามารถเห็นความแตกต่างที่สำคัญระหว่างทั้งสองและนี่คือการใช้เหตุผลทั้งหมดสำหรับข้อเสนอนี้กับปัญหานี้
ตรวจสอบกันเถอะ
เห็นได้ชัดว่ามีหลายคนที่ทำสิ่งนี้แล้วโดยดูที่การจัดสรรฮีปการทดสอบเกณฑ์มาตรฐานการดู RAM แคชและไฟล์เพจ การดูที่จุดข้อมูลเฉพาะและดัชนีการวนซ้ำที่เฉพาะเจาะจงนั้นรวมอยู่ด้วยและการสนทนาต่าง ๆ เกี่ยวกับปัญหานี้ทำให้หลายคนเริ่มตั้งคำถามถึงสิ่งที่เกี่ยวข้องอื่น ๆ เราจะเริ่มมองปัญหานี้โดยใช้อัลกอริธึมทางคณิตศาสตร์และใช้การเปรียบเทียบกับมันอย่างไร เราเริ่มต้นด้วยการยืนยันสองสามอย่าง! จากนั้นเราสร้างอัลกอริทึมของเราจากที่นั่น
คำยืนยันของเรา:
- เราจะให้ลูปและการวนซ้ำของมันเป็นการสรุปที่เริ่มต้นที่ 1 และสิ้นสุดที่ 100000 แทนที่จะเริ่มด้วย 0 ในลูปเพราะเราไม่จำเป็นต้องกังวลเกี่ยวกับรูปแบบการจัดทำดัชนี 0 ของหน่วยความจำเนื่องจากเราสนใจ อัลกอริทึมของตัวเอง
- ในทั้งสองกรณีเรามี 4 ฟังก์ชั่นในการทำงานกับและ 2 ฟังก์ชั่นการโทรด้วย 2 การดำเนินการที่จะทำในแต่ละการเรียกใช้ฟังก์ชั่น เราจะตั้งค่าเหล่านี้เป็นฟังก์ชั่นและการโทรไปยังฟังก์ชั่นเป็นต่อไปนี้:
F1()
, F2()
, f(a)
, f(b)
, และf(c)
f(d)
อัลกอริทึม:
กรณีที่ 1: - การรวมหนึ่งครั้งเดียว แต่การเรียกใช้ฟังก์ชั่นอิสระสองครั้ง
Sum n=1 : [1,100000] = F1(), F2();
F1() = { f(a) = f(a) + f(b); }
F2() = { f(c) = f(c) + f(d); }
กรณีที่สอง: - การสรุปสองครั้ง แต่แต่ละตัวมีการเรียกใช้ฟังก์ชันของตัวเอง
Sum1 n=1 : [1,100000] = F1();
F1() = { f(a) = f(a) + f(b); }
Sum2 n=1 : [1,100000] = F1();
F1() = { f(c) = f(c) + f(d); }
ถ้าคุณสังเกตเห็นF2()
มีอยู่เพียงในSum
จากCase1
ที่F1()
มีอยู่ในSum
จากCase1
และทั้งในSum1
และจากSum2
Case2
สิ่งนี้จะปรากฏในภายหลังเมื่อเราเริ่มสรุปว่ามีการเพิ่มประสิทธิภาพที่เกิดขึ้นภายในอัลกอริทึมที่สอง
การวนซ้ำผ่านการSum
เรียกใช้เคสแรกf(a)
ที่จะเพิ่มเข้ากับตัวมันเองf(b)
จากนั้นจะเรียกf(c)
ว่าจะทำเหมือนเดิม แต่เพิ่มf(d)
ไปยังตัวเองสำหรับ100000
การวนซ้ำแต่ละครั้ง ในกรณีที่สองเรามีSum1
และSum2
ที่ทั้งสองทำหน้าที่เหมือนกับว่าพวกเขาเป็นฟังก์ชั่นเดียวกันที่ถูกเรียกสองครั้งในแถว
ในกรณีนี้เราสามารถปฏิบัติต่อSum1
และSum2
เป็นเพียงแค่เก่าSum
ที่Sum
ในกรณีนี้มีลักษณะเช่นนี้Sum n=1 : [1,100000] { f(a) = f(a) + f(b); }
และตอนนี้ดูเหมือนว่าการเพิ่มประสิทธิภาพที่เราสามารถพิจารณาให้มันเป็นฟังก์ชั่นเดียวกัน
สรุปด้วยระบบอะนาล็อก
จากสิ่งที่เราเห็นในกรณีที่สองมันเกือบจะปรากฏขึ้นราวกับว่ามีการเพิ่มประสิทธิภาพเนื่องจากลูปทั้งคู่มีลายเซ็นที่เหมือนกัน แต่นี่ไม่ใช่ปัญหาจริง ปัญหาไม่ได้เป็นงานที่จะถูกทำโดยf(a)
, f(b)
, f(c)
และf(d)
และในทั้งสองกรณีและการเปรียบเทียบระหว่างสองกรณีนี้คือความแตกต่างของระยะทางที่การสรุปจะต้องเดินทางในแต่ละกรณีที่ให้ความแตกต่างในเวลาดำเนินการ
คิดว่าการFor Loops
เป็นคนSummations
ทำซ้ำเป็นการBoss
สั่งให้คนสองคนA
& B
และงานของพวกเขาคือการกินเนื้อC
& D
ตามลำดับและรับพัสดุจากพวกเขาและส่งคืน Boss
ในการเปรียบเทียบนี้สำหรับลูปหรือทำซ้ำบวกและการตรวจสอบสภาพตัวเองไม่จริงเป็นตัวแทนของ สิ่งที่แสดงถึงBoss
ไม่ได้มาจากอัลกอริทึมทางคณิตศาสตร์ที่เกิดขึ้นจริงโดยตรง แต่จากแนวคิดที่แท้จริงของScope
และCode Block
ภายในรูทีนย่อยหรือรูทีนย่อยวิธีการฟังก์ชั่นหน่วยแปล ฯลฯ อัลกอริทึมแรกมี 1 ขอบเขต
ภายในกรณีแรกในแต่ละสลิปการโทรBoss
ไปที่A
และให้คำสั่งซื้อและA
ออกไปเรียกB's
แพคเกจจากนั้นBoss
ไปที่C
และให้คำสั่งซื้อเพื่อทำแบบเดียวกันและรับแพคเกจจากD
ในแต่ละการทำซ้ำ
ภายในกรณีที่สองการBoss
ทำงานโดยตรงกับA
ไปและดึงB's
แพคเกจจนกว่าจะได้รับแพคเกจทั้งหมด จากนั้นการBoss
ทำงานด้วยC
จะทำเช่นเดียวกันเพื่อรับD's
แพ็คเกจทั้งหมด
เนื่องจากเรากำลังทำงานกับตัวชี้ขนาด 8 ไบต์และจัดการกับการจัดสรรฮีปลองพิจารณาปัญหาต่อไปนี้ ขอบอกว่าBoss
เป็น 100 ฟุตจากA
และที่A
อยู่ที่ 500 C
ฟุตจาก เราไม่จำเป็นต้องกังวลว่าในBoss
ตอนแรกจะมีจำนวนเท่าไรC
เนื่องจากลำดับของการประหารชีวิต ในทั้งสองกรณีBoss
แรกเดินทางจากครั้งแรกแล้วA
B
การเปรียบเทียบนี้ไม่ได้บอกว่าระยะทางนี้แน่นอน มันเป็นเพียงสถานการณ์กรณีทดสอบที่มีประโยชน์เพื่อแสดงการทำงานของอัลกอริทึม
ในหลายกรณีเมื่อทำการจัดสรรฮีปและทำงานกับแคชและไฟล์เพจระยะทางเหล่านี้ระหว่างตำแหน่งที่อยู่อาจไม่แตกต่างกันมากหรืออาจแตกต่างกันอย่างมีนัยสำคัญขึ้นอยู่กับลักษณะของชนิดข้อมูลและขนาดอาร์เรย์
กรณีทดสอบ:
กรณีแรก:ในการทำซ้ำBoss
ครั้งแรกต้องไป 100 ฟุตในขั้นแรกเพื่อให้สลิปสั่งซื้อA
และA
ไปและทำสิ่งของเขา แต่แล้วBoss
ต้องเดินทาง 500 ฟุตC
เพื่อให้เขาสลิปสั่งซื้อของเขา จากนั้นในการทำซ้ำครั้งต่อไปและการทำซ้ำทุก ๆ ครั้งหลังจากBoss
นั้นจะต้องย้อนกลับไป 500 ฟุตระหว่างทั้งสอง
กรณีที่สอง:ผู้ที่Boss
ต้องเดินทาง 100 ฟุตในการทำซ้ำครั้งแรกไปA
แต่หลังจากนั้นเขาก็อยู่ที่นั่นแล้วและรอที่A
จะกลับไปจนกว่าบิลทั้งหมดจะถูกเติมเต็ม จากนั้นBoss
มีการเดินทาง 500 ฟุตบนย้ำแรกที่จะC
เพราะC
เป็น 500 A
ฟุตจาก เนื่องจากสิ่งนี้Boss( Summation, For Loop )
ถูกเรียกใช้ทันทีหลังจากทำงานกับA
เขาจากนั้นก็รออยู่ตรงนั้นเหมือนที่เขาทำA
จนกระทั่งC's
ใบคำสั่งทั้งหมดเสร็จสิ้น
ความแตกต่างในระยะทางที่เดินทาง
const n = 100000
distTraveledOfFirst = (100 + 500) + ((n-1)*(500 + 500);
// Simplify
distTraveledOfFirst = 600 + (99999*100);
distTraveledOfFirst = 600 + 9999900;
distTraveledOfFirst = 10000500;
// Distance Traveled On First Algorithm = 10,000,500ft
distTraveledOfSecond = 100 + 500 = 600;
// Distance Traveled On Second Algorithm = 600ft;
การเปรียบเทียบค่า Arbitrary
เราสามารถเห็นได้อย่างง่ายดายว่า 600 นั้นน้อยกว่า 10 ล้าน ตอนนี้มันไม่ถูกต้องเพราะเราไม่ทราบความแตกต่างที่แท้จริงของระยะทางระหว่างที่อยู่ของ RAM หรือ Cache หรือ Page File แต่ละครั้งที่มีการเรียกในการวนซ้ำแต่ละครั้งจะเป็นเพราะตัวแปรที่มองไม่เห็นอื่น ๆ นี่เป็นเพียงการประเมินสถานการณ์ที่ต้องระวังและมองจากสถานการณ์ที่เลวร้ายที่สุด
จากตัวเลขเหล่านี้มันจะปรากฏราวกับว่าอัลกอริทึมหนึ่งควร99%
ช้ากว่าอัลกอริทึมสอง แต่นี้เป็นเพียงBoss's
บางส่วนหรือความรับผิดชอบของอัลกอริทึมและมันไม่บัญชีสำหรับคนงานที่เกิดขึ้นจริงA
, B
, C
และD
และสิ่งที่พวกเขาต้องทำในแต่ละและทุกทวนของวง ดังนั้นงานของเจ้านายมีเพียงประมาณ 15 - 40% ของงานที่ทำทั้งหมด จำนวนงานที่ทำผ่านคนงานมีผลกระทบใหญ่กว่าเล็กน้อยต่อการรักษาอัตราส่วนของความแตกต่างของอัตราความเร็วประมาณ 50-70%
การสังเกต: - ความแตกต่างระหว่างสองอัลกอริธึม
ในสถานการณ์นี้มันเป็นโครงสร้างของกระบวนการของงานที่ทำ มันจะแสดงให้เห็นว่ากรณีที่ 2มีประสิทธิภาพมากขึ้นจากทั้งการเพิ่มประสิทธิภาพบางส่วนของการมีฟังก์ชั่นการประกาศที่คล้ายกันและคำนิยามที่เป็นเพียงตัวแปรที่แตกต่างกันตามชื่อและระยะทางที่เดินทาง
เรายังเห็นว่าระยะทางทั้งหมดที่เดินทางไปในกรณีที่ 1นั้นไกลกว่าในกรณีที่ 2 มากและเราสามารถพิจารณาระยะนี้ได้เดินทางปัจจัยเวลาของเราระหว่างอัลกอริธึมทั้งสอง กรณีที่ 1มีการทำงานมากขึ้นมากในการทำกว่ากรณีที่ 2ไม่
สิ่งนี้สามารถสังเกตได้จากหลักฐานของASM
คำแนะนำที่ปรากฏในทั้งสองกรณี นอกเหนือจากที่ระบุไว้เกี่ยวกับกรณีเหล่านี้แล้วสิ่งนี้ไม่ได้คำนึงถึงความจริงที่ว่าในกรณีที่ 1หัวหน้าจะต้องรอให้ทั้งสองA
& C
กลับไปก่อนที่เขาจะกลับไปA
อีกครั้งสำหรับการทำซ้ำแต่ละครั้ง นอกจากนี้ยังไม่ได้คำนึงถึงความจริงที่ว่าหากA
หรือB
ใช้เวลานานมากจากนั้นทั้งBoss
คนงานและคนงานคนอื่น ๆ กำลังรอการประหารชีวิต
ในกรณีที่ 2สิ่งเดียวที่ไม่ได้ทำงานคือBoss
จนกว่าผู้ปฏิบัติงานจะกลับมา ดังนั้นแม้จะมีผลกระทบต่ออัลกอริทึม
คำถามที่แก้ไขเพิ่มเติม
แก้ไข: คำถามกลายเป็นว่าไม่มีความเกี่ยวข้องเนื่องจากพฤติกรรมที่รุนแรงขึ้นอยู่กับขนาดของอาร์เรย์ (n) และแคช CPU ดังนั้นหากมีสิ่งที่น่าสนใจเพิ่มเติมฉันขอใช้คำถามใหม่:
คุณสามารถให้รายละเอียดเชิงลึกเกี่ยวกับรายละเอียดที่นำไปสู่พฤติกรรมแคชที่แตกต่างกันซึ่งแสดงโดยห้าภูมิภาคในกราฟต่อไปนี้หรือไม่
อาจเป็นเรื่องที่น่าสนใจที่จะชี้ให้เห็นความแตกต่างระหว่างสถาปัตยกรรมของ CPU / แคชโดยจัดทำกราฟที่คล้ายกันสำหรับ CPU เหล่านี้
เกี่ยวกับคำถามเหล่านี้
อย่างที่ฉันได้แสดงให้เห็นอย่างไม่ต้องสงสัยมีปัญหาพื้นฐานก่อนที่จะมีส่วนร่วมของฮาร์ดแวร์และซอฟต์แวร์
ขณะนี้สำหรับการจัดการหน่วยความจำและแคชพร้อมกับไฟล์หน้า ฯลฯ ซึ่งทั้งหมดทำงานร่วมกันในชุดรวมของระบบระหว่างต่อไปนี้:
The Architecture
{ฮาร์ดแวร์, เฟิร์มแวร์, ไดรเวอร์แบบฝัง, ชุดคำสั่ง Kernels และ ASM}
The OS
{ระบบจัดการไฟล์และหน่วยความจำไดรเวอร์และรีจิสตรี}
The Compiler
{หน่วยการแปลและการเพิ่มประสิทธิภาพของซอร์สโค้ด}
- และแม้แต่
Source Code
ตัวมันเองด้วยชุดของอัลกอริธึมที่โดดเด่น
แล้วเราจะเห็นว่ามีเป็นคอขวดที่เกิดขึ้นในขั้นตอนวิธีแรกก่อนที่เราจะนำไปใช้กับเครื่องใด ๆ กับการใด ๆ โดยพลArchitecture
, OS
และProgrammable Language
เมื่อเทียบกับอัลกอริทึมที่สอง มีปัญหาอยู่แล้วก่อนที่จะเกี่ยวข้องกับอินทรินสิกส์ของคอมพิวเตอร์สมัยใหม่
ผลลัพธ์ที่สิ้นสุด
อย่างไรก็ตาม; ไม่ใช่การบอกว่าคำถามใหม่เหล่านี้ไม่มีความสำคัญเพราะเป็นของตัวเองและมีบทบาทสำคัญ พวกเขาส่งผลกระทบต่อขั้นตอนและประสิทธิภาพโดยรวมซึ่งเห็นได้ชัดจากกราฟและการประเมินที่หลากหลายจากหลาย ๆ คนที่ได้รับคำตอบและ / หรือแสดงความคิดเห็น
หากคุณให้ความสนใจกับการเปรียบเทียบของBoss
และคนงานสองคนA
& B
ผู้ที่ต้องไปและดึงแพ็คเกจจากC
& D
ตามลำดับและพิจารณาสัญกรณ์ทางคณิตศาสตร์ของอัลกอริธึมที่เป็นปัญหา คุณสามารถดูได้โดยไม่ต้องมีส่วนร่วมของคอมพิวเตอร์ฮาร์ดแวร์และซอฟแวร์Case 2
จะอยู่ที่ประมาณได้เร็วกว่า60%
Case 1
เมื่อคุณดูกราฟและแผนภูมิหลังจากอัลกอริธึมเหล่านี้ถูกนำไปใช้กับซอร์สโค้ดบางส่วนรวบรวมเพิ่มประสิทธิภาพและดำเนินการผ่านระบบปฏิบัติการเพื่อดำเนินการกับฮาร์ดแวร์ที่กำหนดคุณสามารถเห็นความแตกต่างระหว่างความแตกต่างเล็กน้อย ในอัลกอริทึมเหล่านี้
หากData
ชุดมีขนาดเล็กพอสมควรก็อาจจะดูไม่แตกต่างกันในตอนแรก แต่เนื่องจากCase 1
เป็นเรื่องเกี่ยวกับ60 - 70%
ช้ากว่าCase 2
ที่เราสามารถมองไปที่การเจริญเติบโตของฟังก์ชั่นนี้ในแง่ของความแตกต่างในการประหารชีวิตเวลา:
DeltaTimeDifference approximately = Loop1(time) - Loop2(time)
//where
Loop1(time) = Loop2(time) + (Loop2(time)*[0.6,0.7]) // approximately
// So when we substitute this back into the difference equation we end up with
DeltaTimeDifference approximately = (Loop2(time) + (Loop2(time)*[0.6,0.7])) - Loop2(time)
// And finally we can simplify this to
DeltaTimeDifference approximately = [0.6,0.7]*Loop2(time)
การประมาณนี้เป็นความแตกต่างโดยเฉลี่ยระหว่างสองลูปทั้งการดำเนินการแบบอัลกอริทึมและการทำงานของเครื่องที่เกี่ยวข้องกับการปรับแต่งซอฟต์แวร์และคำสั่งเครื่อง
เมื่อชุดข้อมูลเติบโตเป็นเส้นตรงดังนั้นความแตกต่างของเวลาระหว่างสองชุด อัลกอริทึม 1 มีการดึงข้อมูลมากกว่าอัลกอริทึม 2 ซึ่งเห็นได้ชัดว่าเมื่อBoss
ต้องเดินทางไปมาระยะทางสูงสุดระหว่างA
& C
สำหรับการวนซ้ำทุกครั้งหลังจากการวนซ้ำครั้งแรกในขณะที่อัลกอริทึม 2 Boss
ต้องเดินทางไปA
ครั้งเดียวแล้วหลังจากA
เดินทาง เป็นระยะทางสูงสุดเพียงครั้งเดียวเมื่อจะจากไปA
C
พยายามที่จะให้ความBoss
สำคัญกับการทำสองสิ่งที่คล้ายกันในเวลาเดียวกันและเล่นกลพวกเขาไปมาแทนที่จะมุ่งเน้นไปที่งานต่อเนื่องที่คล้ายกันจะทำให้เขาโกรธมากในตอนท้ายของวันเนื่องจากเขาต้องเดินทางและทำงานเป็นสองเท่า ดังนั้นอย่าสูญเสียขอบเขตของสถานการณ์โดยปล่อยให้หัวหน้าของคุณเข้าไปในคอขวดที่ถูกแก้ไขเนื่องจากคู่สมรสและลูกของเจ้านายไม่พอใจ
การแก้ไข: หลักการออกแบบวิศวกรรมซอฟต์แวร์
- ความแตกต่างระหว่างLocal Stack
และHeap Allocated
การคำนวณภายในการวนซ้ำและความแตกต่างระหว่างการใช้งานประสิทธิภาพและประสิทธิผล -
อัลกอริทึมทางคณิตศาสตร์ที่ฉันเสนอข้างต้นส่วนใหญ่ใช้กับลูปที่ดำเนินการกับข้อมูลที่จัดสรรในฮีป
- การดำเนินการสแต็กติดต่อกัน:
- หากลูปดำเนินการกับข้อมูลในเครื่องภายในบล็อคโค้ดเดียวหรือขอบเขตที่อยู่ภายในกรอบสแต็กมันจะยังคงเรียงลำดับ แต่ตำแหน่งหน่วยความจำจะอยู่ใกล้มากขึ้นโดยที่พวกมันมักจะเรียงตามลำดับและความแตกต่างของระยะทาง เกือบจะเล็กน้อย เนื่องจากไม่มีการจัดสรรภายในฮีปหน่วยความจำจึงไม่กระจัดกระจายและหน่วยความจำจะไม่ถูกเรียกผ่าน ram โดยทั่วไปหน่วยความจำจะเรียงตามลำดับและสัมพันธ์กับกรอบสแต็กและตัวชี้สแต็ก
- เมื่อการดำเนินการติดต่อกันบนสแต็คตัวประมวลผลที่ทันสมัยจะแคชค่าซ้ำ ๆ และที่อยู่ที่เก็บค่าเหล่านี้ภายในการลงทะเบียนแคชภายในเครื่อง เวลาของการปฏิบัติงานหรือคำแนะนำที่นี่อยู่ในลำดับของนาโนวินาที
- การจัดสรรฮีปแบบต่อเนื่อง:
- เมื่อคุณเริ่มใช้การจัดสรรฮีปและตัวประมวลผลต้องดึงที่อยู่หน่วยความจำในการโทรติดต่อกันขึ้นอยู่กับสถาปัตยกรรมของ CPU ตัวควบคุมบัสและโมดูลแรมเวลาของการดำเนินการหรือการประมวลผลสามารถอยู่ในลำดับของไมโคร มิลลิวินาที เมื่อเปรียบเทียบกับการดำเนินการสแต็กแคชเหล่านี้ค่อนข้างช้า
- CPU จะต้องดึงข้อมูลที่อยู่หน่วยความจำจาก Ram และโดยทั่วไปสิ่งใดก็ตามในบัสระบบจะช้ากว่าเมื่อเปรียบเทียบกับพา ธ ข้อมูลภายในหรือบัสข้อมูลภายใน CPU เอง
ดังนั้นเมื่อคุณทำงานกับข้อมูลที่ต้องอยู่ใน heap และคุณกำลังเข้าไปสำรวจในลูปมันจะมีประสิทธิภาพมากกว่าในการเก็บชุดข้อมูลแต่ละชุดและอัลกอริธึมที่สอดคล้องกันภายในลูปเดี่ยวของมันเอง คุณจะได้รับการเพิ่มประสิทธิภาพที่ดีขึ้นเมื่อเทียบกับการพยายามแยกลูปออกจากกันโดยการดำเนินการหลายอย่างของชุดข้อมูลที่แตกต่างกันที่อยู่ใน heap ไว้ในลูปเดียว
การทำเช่นนี้กับข้อมูลที่อยู่ในสแต็กเนื่องจากมีการแคชบ่อยครั้ง แต่ไม่ใช่สำหรับข้อมูลที่ต้องมีที่อยู่หน่วยความจำของมันทุกครั้ง
นี่คือที่มาของวิศวกรรมซอฟต์แวร์และการออกแบบสถาปัตยกรรมซอฟต์แวร์ มันเป็นความสามารถในการรู้วิธีการจัดระเบียบข้อมูลของคุณรู้ว่าเมื่อใดที่จะเก็บข้อมูลของคุณรู้ว่าเมื่อไหร่ที่จะจัดสรรข้อมูลของคุณบนกองรู้วิธีการออกแบบและใช้อัลกอริทึมของคุณและรู้ว่าเมื่อใดและที่ไหน
คุณอาจมีอัลกอริทึมเดียวกันที่เกี่ยวข้องกับชุดข้อมูลเดียวกัน แต่คุณอาจต้องการการออกแบบการใช้งานหนึ่งสำหรับชุดตัวแปรสแต็กและอีกชุดสำหรับชุดตัวแปรจัดสรรฮีปเนื่องจากปัญหาข้างต้นที่เห็นได้จากO(n)
ความซับซ้อนของอัลกอริทึมเมื่อทำงาน กับกอง
จากสิ่งที่ฉันสังเกตเห็นในช่วงหลายปีที่ผ่านมาหลายคนไม่ได้คำนึงถึงข้อเท็จจริงนี้ พวกเขาจะมีแนวโน้มที่จะออกแบบอัลกอริทึมหนึ่งที่ทำงานกับชุดข้อมูลเฉพาะและพวกเขาจะใช้มันโดยไม่คำนึงถึงชุดข้อมูลที่ถูกแคชในท้องถิ่นในสแต็กหรือถ้ามันถูกจัดสรรในกอง
หากคุณต้องการการเพิ่มประสิทธิภาพที่แท้จริงใช่มันอาจดูเหมือนว่าการทำสำเนารหัส แต่โดยทั่วไปแล้วมันจะมีประสิทธิภาพมากขึ้นที่จะมีสองตัวแปรของอัลกอริทึมเดียวกัน หนึ่งสำหรับการดำเนินการสแต็กและอื่น ๆ สำหรับการดำเนินการกองที่ดำเนินการในลูปซ้ำ!
นี่คือตัวอย่างเทียม: สองโครงสร้างอย่างง่ายหนึ่งอัลกอริทึม
struct A {
int data;
A() : data{0}{}
A(int a) : data{a}{}
};
struct B {
int data;
B() : data{0}{}
A(int b) : data{b}{}
}
template<typename T>
void Foo( T& t ) {
// do something with t
}
// some looping operation: first stack then heap.
// stack data:
A dataSetA[10] = {};
B dataSetB[10] = {};
// For stack operations this is okay and efficient
for (int i = 0; i < 10; i++ ) {
Foo(dataSetA[i]);
Foo(dataSetB[i]);
}
// If the above two were on the heap then performing
// the same algorithm to both within the same loop
// will create that bottleneck
A* dataSetA = new [] A();
B* dataSetB = new [] B();
for ( int i = 0; i < 10; i++ ) {
Foo(dataSetA[i]); // dataSetA is on the heap here
Foo(dataSetB[i]); // dataSetB is on the heap here
} // this will be inefficient.
// To improve the efficiency above, put them into separate loops...
for (int i = 0; i < 10; i++ ) {
Foo(dataSetA[i]);
}
for (int i = 0; i < 10; i++ ) {
Foo(dataSetB[i]);
}
// This will be much more efficient than above.
// The code isn't perfect syntax, it's only psuedo code
// to illustrate a point.
นี่คือสิ่งที่ฉันอ้างถึงโดยมีการใช้งานแยกต่างหากสำหรับตัวแปรสแต็กกับพันธุ์ฮีป อัลกอริธึมเองนั้นไม่สำคัญมากนักมันเป็นโครงสร้างวนลูปที่คุณจะใช้ในการทำเช่นนั้น