เทคนิคการกลับด้านลูปคืออะไร?


89

ฉันกำลังอ่านเอกสารที่พูดถึงเทคนิคการเพิ่มประสิทธิภาพคอมไพเลอร์แบบทันเวลา (JIT) สำหรับ Java หนึ่งในนั้นคือ "loop inversion" และเอกสารระบุว่า:

คุณแทนที่whileลูปปกติด้วยdo-whileลูป และ do-whileลูปถูกตั้งค่าภายในifอนุประโยค การเปลี่ยนนี้ทำให้กระโดดน้อยลงสองครั้ง

การผกผันของลูปทำงานอย่างไรและเพิ่มประสิทธิภาพเส้นทางรหัสของเราอย่างไร

หมายเหตุ: จะดีมากถ้าใครสักคนสามารถอธิบายด้วยตัวอย่างของโค้ด Java และวิธีที่ JIT ปรับให้เหมาะสมกับโค้ดเนทีฟและเหตุใดจึงเหมาะสมที่สุดในโปรเซสเซอร์สมัยใหม่


2
ไม่ใช่สิ่งที่คุณจะทำกับซอร์สโค้ดของคุณ มันเกิดขึ้นในระดับโค้ดเนทีฟ
Marko Topolnik

2
@MarkoTopolnik ฉันรู้ แต่ฉันอยากรู้ว่า JIT ทำสิ่งนี้ในระดับโค้ดเนทีฟได้อย่างไร ขอบคุณ.
ลอง

1
โอ้เย็นมีหน้าวิกิพีเดียเกี่ยวกับเรื่องนี้ที่มีมากมายของตัวอย่างen.wikipedia.org/wiki/Loop_inversion ตัวอย่าง C ใช้ได้เช่นเดียวกับใน Java
Benjamin Gruenbaum

เมื่อไม่นานมานี้ได้รับแรงบันดาลใจจากคำถามหนึ่งใน SO ฉันได้ทำการวิจัยสั้น ๆ ในเรื่องนี้บางทีผลลัพธ์อาจจะเป็นประโยชน์กับคุณ: stackoverflow.com/questions/16205843/java-loop-efficiency/…
Adam Siemion

นี่เป็นสิ่งเดียวกับที่เงื่อนไขการวนซ้ำมักจะถูกใส่ไว้ในตอนท้าย (ไม่ว่าจะมีการกระโดดน้อยลงหรือไม่) เพียงเพื่อให้มีคำแนะนำในการกระโดดน้อยลง (1 ต่อ 2 ต่อการวนซ้ำ)
extremeaxe5

คำตอบ:


108
while (condition) { 
  ... 
}

เวิร์กโฟลว์:

  1. ตรวจสอบสภาพ;
  2. ถ้าเป็นเท็จให้ข้ามไปที่ด้านนอกของลูป
  3. เรียกใช้การวนซ้ำหนึ่งครั้ง
  4. ข้ามไปด้านบน

if (condition) do {
  ...
} while (condition);

เวิร์กโฟลว์:

  1. ตรวจสอบสภาพ;
  2. ถ้าเป็นเท็จให้ข้ามไปที่ด้านนอกลูป
  3. เรียกใช้การวนซ้ำหนึ่งครั้ง
  4. ตรวจสอบสภาพ;
  5. ถ้าเป็นจริงข้ามไปยังขั้นตอนที่ 3

การเปรียบเทียบทั้งสองนี้คุณจะเห็นได้อย่างง่ายดายว่าการกระโดดครั้งหลังอาจไม่ได้ทำการกระโดดใด ๆ เลยโดยมีเงื่อนไขว่ามีขั้นตอนเดียวในการวนซ้ำและโดยทั่วไปจำนวนการกระโดดจะน้อยกว่าจำนวนการวนซ้ำหนึ่งครั้ง อดีตจะต้องย้อนกลับไปเพื่อตรวจสอบเงื่อนไขเพียงเพื่อกระโดดออกจากลูปเมื่อเงื่อนไขเป็นเท็จ

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

การอธิบายการทำนายสาขาที่กล่าวถึง: สำหรับการกระโดดแบบมีเงื่อนไขแต่ละประเภท CPU จะมีคำสั่งสองคำสั่งแต่ละคำสั่งรวมถึงการเดิมพันผลลัพธ์ ตัวอย่างเช่นคุณจะใส่คำสั่งว่า " jump if not zero, bet on not zero " ที่ตอนท้ายของลูปเพราะการกระโดดจะต้องทำซ้ำทั้งหมดยกเว้นครั้งสุดท้าย ด้วยวิธีนี้ซีพียูจะเริ่มสูบไปป์ไลน์โดยทำตามคำแนะนำตามเป้าหมายการกระโดดแทนที่จะเป็นตามคำสั่งกระโดด

โน๊ตสำคัญ

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


51
หมายเหตุในตอนท้ายเป็นสิ่งที่สำคัญมากอย่างแน่นอน
TJ Crowder

2
@AdamSiemion: bytecode ที่สร้างขึ้นสำหรับdo-whileซอร์สโค้ดที่ระบุนั้นไม่เกี่ยวข้องเพราะเราไม่ได้เขียนอย่างนั้นจริงๆ เราเขียนwhileลูปและปล่อยให้คอมไพเลอร์และ JIT สมคบคิดเพื่อปรับปรุงให้เรา (ผ่านการผกผันของลูป) ถ้า / ตามความจำเป็น
TJ Crowder

1
@TJCrowder +1 สำหรับข้างต้นรวมถึงหมายเหตุถึง Adam: อย่าพิจารณา bytecode เมื่อคิดถึงการเพิ่มประสิทธิภาพของคอมไพเลอร์ JIT Bytecode ใกล้เคียงกับซอร์สโค้ด Java มากกว่าโค้ดที่คอมไพล์ JIT จริงที่กำลังดำเนินการ ในความเป็นจริงแนวโน้มของภาษาสมัยใหม่ไม่จำเป็นต้องมี bytecode เลยเป็นส่วนหนึ่งของ spcefication ของภาษา
Marko Topolnik

1
จะเป็นข้อมูลเพิ่มเติมหมายเหตุสำคัญได้รับการอธิบายเพิ่มเติมเล็กน้อย ทำไมต้องเข้าใจผิดทั้งหมด
arsaKasra

2
@arsaKasra มันเข้าใจผิดเพราะโดยทั่วไปความสามารถในการอ่านและความเสถียรดีกว่าการเพิ่มประสิทธิภาพในซอร์สโค้ด โดยเฉพาะอย่างยิ่งเมื่อมีการเปิดเผยว่า JIT ทำสิ่งนี้ให้กับคุณคุณไม่ควรพยายามเพิ่มประสิทธิภาพ (ไมโครมาก) ด้วยตัวคุณเอง
Radiodef

24

สิ่งนี้สามารถเพิ่มประสิทธิภาพลูปที่ดำเนินการอย่างน้อยหนึ่งครั้งเสมอ

whileจากนั้นลูปปกติจะข้ามกลับไปที่จุดเริ่มต้นอย่างน้อยหนึ่งครั้งและข้ามไปที่จุดสิ้นสุดหนึ่งครั้งในตอนท้าย ตัวอย่างของการวนซ้ำง่ายๆที่ทำงานครั้งเดียว:

int i = 0;
while (i++ < 1) {
    //do something
}  

do-whileในทางกลับกันการวนซ้ำจะข้ามการกระโดดครั้งแรกและครั้งสุดท้าย นี่คือการวนซ้ำที่เทียบเท่ากับด้านบนซึ่งจะทำงานโดยไม่ต้องกระโดด:

int i = 0;
if (i++ < 1) {
    do {
        //do something
    } while (i++ < 1); 
}

+1 เพื่อความถูกต้องและอันดับแรกโปรดพิจารณาเพิ่มตัวอย่างโค้ด สิ่งที่ต้องการboolean b = true; while(b){ b = maybeTrue();}ที่จะboolean b;do{ b = maybeTrue();}while(b);ควรจะพอเพียง
Benjamin Gruenbaum

ไม่ต้องห่วง. มันทำให้บรรทัดเปิดของคำตอบไม่ถูกต้อง fwiw :-)
TJ Crowder

@TJ ยังไม่เพิ่มประสิทธิภาพลูปที่ไม่ได้ป้อนจะมีการกระโดดครั้งเดียวในทั้งสองกรณี
Keppil

อ่าใช่ ขอโทษค่ะฉันกำลังอ่านนั่นหมายความว่าคุณไม่สามารถนำไปใช้กับลูปที่ไม่วนซ้ำอย่างน้อยหนึ่งครั้ง (แทนที่จะไม่ช่วยอะไร) กับคุณตอนนี้. :-)
TJ Crowder

@Keppil คุณควรพูดให้ชัดเจนว่าในกรณีที่เรามีการทำซ้ำ X จำนวนมากเราจะบันทึกการกระโดดระหว่าง X เพียงครั้งเดียว
Manuel Selva

3

มาดูกัน:

whileรุ่น:

void foo(int n) {
    while (n < 10) {
       use(n);
       ++n;
    }
    done();
}
  1. ก่อนอื่นเราทดสอบnและข้ามไปdone();ที่เงื่อนไขไม่เป็นจริง
  2. nแล้วเราใช้งานและเพิ่มขึ้น
  3. ตอนนี้เรากระโดดกลับไปที่สภาพ
  4. ล้างทำซ้ำ
  5. done()เมื่ออยู่ในสภาพที่ไม่เป็นจริงเราข้ามไป

do-whileรุ่น:

(โปรดจำไว้ว่าเราไม่ได้ทำสิ่งนี้ในซอร์สโค้ด [ซึ่งจะทำให้เกิดปัญหาในการบำรุงรักษา] คอมไพเลอร์ / JIT ทำเพื่อเรา)

void foo(int n) {
    if (n < 10) {
        do {
            use(n);
            ++n;
        }
        while (n < 10);
    }
    done();
}
  1. ก่อนอื่นเราทดสอบnและข้ามไปdone();ที่เงื่อนไขไม่เป็นจริง
  2. nแล้วเราใช้งานและเพิ่มขึ้น
  3. ตอนนี้เราทดสอบเงื่อนไขและย้อนกลับไปหากเป็นจริง
  4. ล้างทำซ้ำ
  5. เมื่ออยู่ในสภาพที่ไม่เป็นจริงเราไหล (ไม่กระโดด) done()เพื่อ

ตัวอย่างเช่นหากnเริ่มต้น9ใหม่เราจะไม่กระโดดข้ามdo-whileเวอร์ชันเลยในขณะที่ในwhileเวอร์ชันเราต้องย้อนกลับไปที่จุดเริ่มต้นทำการทดสอบแล้วข้ามกลับไปที่จุดสิ้นสุดเมื่อเราเห็นว่ามันไม่เป็นความจริง .


3

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

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

Wikipedia มีตัวอย่างที่ดีมากและฉันกำลังอธิบายที่นี่อีกครั้ง

 int i, a[100];
  i = 0;
  while (i < 100) {
    a[i] = 0;
    i++;
  }

จะถูกแปลงโดยคอมไพเลอร์เป็น

  int i, a[100];
  i = 0;
  if (i < 100) {
    do {
      a[i] = 0;
      i++;
    } while (i < 100);
  }

สิ่งนี้แปลเป็นประสิทธิภาพอย่างไร? เมื่อค่าของ i เท่ากับ 99 โปรเซสเซอร์ไม่จำเป็นต้องดำเนินการ GOTO (ซึ่งจำเป็นในกรณีแรก) สิ่งนี้ช่วยเพิ่มประสิทธิภาพ

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