คอมไพเลอร์ JIT แตกต่างจากคอมไพเลอร์ธรรมดาอย่างไร


22

มีหลาย hype เกี่ยวกับคอมไพเลอร์ JIT สำหรับภาษาเช่น Java, Ruby และ Python คอมไพเลอร์ JIT แตกต่างจากคอมไพเลอร์ C / C ++ อย่างไรและทำไมคอมไพเลอร์ที่เขียนขึ้นสำหรับ Java, Ruby หรือ Python เรียกว่าคอมไพเลอร์ JIT ในขณะที่คอมไพเลอร์ C / C ++ จะเรียกว่าคอมไพเลอร์?

คำตอบ:


17

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

ในอีกด้านหนึ่งคอมไพเลอร์ C และ C ++ นั้นไม่ใช่ JIT พวกเขารวบรวมในการยิงครั้งเดียวเพียงครั้งเดียวบนเครื่องของนักพัฒนาแล้วปฏิบัติการที่ผลิต


มีคอมไพเลอร์ JIT ที่คอยติดตามแคชและปรับใช้กลยุทธ์การคอมไพล์ด้วยหรือไม่? @Victor
Martin Berger

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

ฉันได้ยินเรื่องนี้ในบล็อกโพสต์เมื่อนานมาแล้ว คอมไพเลอร์อาจรวบรวมแตกต่างกันขึ้นอยู่กับ CPU ปัจจุบัน ตัวอย่างเช่นมันอาจ vectorize รหัสโดยอัตโนมัติหากตรวจพบว่า CPU รองรับ SSE / AVX เมื่อมีส่วนขยาย SIMD ที่ใหม่กว่าอาจมีการรวบรวมเป็นส่วนเสริมที่ใหม่กว่าดังนั้นโปรแกรมเมอร์ไม่จำเป็นต้องเปลี่ยนแปลงอะไรเลย หรือถ้ามันสามารถจัดการการดำเนินงาน / ข้อมูลเพื่อใช้ประโยชน์จากแคชที่มีขนาดใหญ่ขึ้นหรือเพื่อลดแคช
มิส

@ LưuVĩnhPhúcChào! ฉันยินดีที่จะเชื่อว่า แต่ความแตกต่างคือเช่นชนิดของ CPU หรือขนาดแคชเป็นสิ่งที่ไม่เปลี่ยนแปลงตลอดการคำนวณดังนั้นสามารถทำได้โดยคอมไพเลอร์แบบคงที่เช่นกัน Cache คิดถึง OTOH แบบไดนามิกมาก
Martin Berger

12

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

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

ลองพิจารณาตัวอย่างเช่นโค้ดดังนี้:

m(a : String, b : String, k : Int) {
  val c : Int;
  switch (k) {
    case 0 : { c = 7; break; }
    ...
    case 17 : { c = complicatedMethod(k, a+b); break; }
  }

  return a.length + b.length - c + 2*k;
}

คอมไพเลอร์ปกติไม่สามารถทำอะไรเกี่ยวกับเรื่องนี้มากเกินไป อย่างไรก็ตามคอมไพเลอร์ JIT อาจตรวจพบว่าmมีการเรียกด้วยk==0เหตุผลบางอย่างเท่านั้น (บางสิ่งเช่นนั้นอาจเกิดขึ้นเมื่อมีการเปลี่ยนแปลงรหัสเมื่อเวลาผ่านไป); มันสามารถสร้างโค้ดที่มีขนาดเล็กลง (และคอมไพล์มันให้เป็นรหัสเนทีฟแม้ว่าฉันจะคิดว่านี่เป็นจุดย่อยแนวคิด):

m(a : String, b : String) {
  return a.length + b.length - 7;
}

ณ จุดนี้มันอาจจะเป็นแบบอินไลน์เรียกวิธีที่มันเป็นเรื่องเล็กน้อยในขณะนี้

เห็นได้ชัดว่า Sun ได้ยกเลิกการปรับให้เหมาะสมที่สุดที่javacใช้ใน Java 6 ฉันได้รับแจ้งว่าการปรับให้เหมาะสมเหล่านั้นทำให้มันยากสำหรับ JIT ที่จะทำอะไรมากมายและโค้ดที่คอมไพล์อย่างไร้เดียงสาก็วิ่งได้เร็วขึ้นในตอนท้าย ไปคิด


โดยวิธีการในการปรากฏตัวของการลบประเภท (เช่น generics ใน Java) JIT เป็นประเภท wrt เสียเปรียบ
Raphael

ดังนั้นรันไทม์มีความซับซ้อนมากกว่านั้นสำหรับภาษาที่คอมไพล์เนื่องจากสภาพแวดล้อมรันไทม์ต้องรวบรวมข้อมูลเพื่อปรับให้เหมาะสม
saadtaame

2
ในหลายกรณี JIT จะไม่สามารถแทนที่mด้วยเวอร์ชันที่ไม่ได้ตรวจสอบได้kเนื่องจากจะไม่สามารถพิสูจน์ได้ว่าmจะไม่ถูกเรียกด้วย non-zero kแต่ไม่สามารถพิสูจน์ได้ว่าสามารถแทนที่ได้ กับม.static miss_count; if (k==0) return a.length+b.length-7; else if (miss_count++ < 16) { ... unoptimized code for ...} else { ... consider other optimizations...}
supercat

1
ฉันเห็นด้วยกับ @supercat และคิดว่าตัวอย่างของคุณทำให้เข้าใจผิดได้ ถ้าk=0 เสมอหมายความว่ามันไม่ใช่ฟังก์ชั่นของอินพุตหรือสภาพแวดล้อมปลอดภัยที่จะลดการทดสอบ - แต่การพิจารณาว่าจำเป็นต้องมีการวิเคราะห์แบบสแตติกซึ่งมีค่าใช้จ่ายสูงมาก JIT สามารถชนะเมื่อเส้นทางหนึ่งผ่านบล็อกของโค้ดถูกนำมาใช้บ่อยกว่าอีกเส้นทางหนึ่งและรุ่นของรหัสเฉพาะสำหรับเส้นทางนี้จะเร็วกว่ามาก แต่ JIT จะยังคงปล่อยการทดสอบเพื่อตรวจสอบว่ามีการใช้เส้นทางที่รวดเร็วหรือไม่และใช้ "เส้นทางที่ช้า" ถ้าไม่
j_random_hacker

ตัวอย่าง: ฟังก์ชันรับBase* pพารามิเตอร์และเรียกใช้ฟังก์ชันเสมือนผ่านมัน การวิเคราะห์รันไทม์แสดงให้เห็นว่าวัตถุจริงชี้ไปที่เสมอ (หรือเกือบตลอดเวลา) ดูเหมือนว่าจะเป็นDerived1ประเภท JIT สามารถสร้างรุ่นใหม่ของฟังก์ชั่นที่มีการเรียกแบบคงที่ (หรือ inlined) เรียกDerived1วิธีการ รหัสนี้จะนำหน้าด้วยเงื่อนไขที่ตรวจสอบว่าpตัวชี้ vtable ชี้ไปที่Derived1ตารางที่คาดไว้หรือไม่ ถ้าไม่ใช่จะเป็นการข้ามไปยังเวอร์ชันดั้งเดิมของฟังก์ชันแทนที่จะเรียกวิธีที่ช้ากว่าแบบไดนามิก
j_random_hacker
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.