JIT (Just-In-Time) เป็นเทคโนโลยีการคอมไพล์แบบไดนามิกที่แปลงไบต์โค้ดหรือตัวแทนกลางของโปรแกรมเป็นคำสั่งเครื่องโดยตรงระหว่างการทำงาน ใน Android คอมไพเลอร์ JIT ปรากฏครั้งแรกในเวอร์ชัน 2.2 Froyo ซึ่งเป็นส่วนหนึ่งของเครื่องเสมือน Dalvik และเพิ่มความเร็วในการทำงานของแอปพลิเคชัน 2–5 เท่า ตามข้อมูลของ Google, 2024 JIT สมัยใหม่ใน ART ผสานการแปลความหมายกับการคอมไพล์แบบมีโปรไฟล์ของเมธอดร้อน
ประเด็นสำคัญ
Just-In-Time (JIT) เป็นวิธีการคอมไพล์ที่ซอร์สโค้ดหรือไบต์โค้ดถูกแปลงเป็นคำสั่งเครื่องไม่ใช่ล่วงหน้า (เหมือนกับ AOT) แต่ในขณะที่มีการเรียกส่วนที่เกี่ยวข้องของโปรแกรมครั้งแรก คำว่า “Just-In-Time” หมายถึงการคอมไพล์เกิดขึ้น “ทันเวลา” — ทันทีก่อนการทำงาน
แนวคิดของ JIT มีมาตั้งแต่ทศวรรษ 1960 แต่ได้รับการยอมรับอย่างกว้างขวางกับการถือกำเนิดของ Java Virtual Machine ในปี 1995 JIT ช่วยให้สามารถรวมการพกพาของไบต์โค้ด (เขียนครั้งเดียว — ทำงานได้ทุกที่) เข้ากับประสิทธิภาพที่ใกล้เคียงกับโค้ดพื้นเมือง ใน Java HotSpot VM คอมไพเลอร์ JIT วิเคราะห์โค้ดที่ทำงานและคอมไพล์เฉพาะส่วนที่สำคัญที่สุด ประหยัดเวลาและหน่วยความจำ
คอมไพเลอร์ JIT รับไบต์โค้ดเป็นอินพุต แปลความหมาย และรวบรวมสถิติไปพร้อมกัน เมื่อส่วนของโค้ด (เมธอด, ลูป) ถูกเรียกบ่อยเพียงพอ JIT จะตัดสินใจคอมไพล์มัน โค้ดเครื่องที่คอมไพล์แล้วจะถูกเก็บในแคช — ในการเรียกครั้งถัดไป จะใช้เวอร์ชันที่คอมไพล์แล้ว ซึ่งให้ความเร็วโดยไม่ต้องคอมไพล์ทั้งโปรแกรม
// ตัวอย่าง: เมธอดจะกลายเป็นร้อนหลังจากการเรียกหลายครั้ง
public class HotMethod {
private int compute(int n) {
int sum = 0;
for (int i = 0; i < n; i++) {
sum += i * i;
}
return sum;
}
}
// เรียก 500 ครั้งในลูป — JIT จะคอมไพล์ compute
for (int t = 0; t < 500; t++) {
hot.compute(1000);
}
ใน Android การคอมไพล์ JIT ผ่านสามช่วงของวิวัฒนาการ ช่วงแรก — Dalvik ที่ไม่มี JIT (Android 1.0–2.1): การแปลความหมายบริสุทธิ์ของไบต์โค้ด DEX ช่วงที่สอง — Dalvik ที่มี JIT (Android 2.2–4.4): การเปิดตัวคอมไพเลอร์ JIT ซึ่งเพิ่มความเร็วแอปพลิเคชัน 2–5 เท่า ช่วงที่สาม — ART ที่มี JIT แบบไฮบริด (Android 7.0+): การกลับมาของ JIT ในความสามารถใหม่
JIT ใน Dalvik ถูกนำมาใช้เป็นคอมไพเลอร์ แบบร่องรอย มันไม่ได้วิเคราะห์เมธอดแต่ละเมธอด แต่เป็นลูกโซ่ของคำสั่ง (ร่องรอย) ที่ทำงานตามลำดับบ่อยครั้ง สิ่งนี้ช่วยให้สามารถคอมไพล์เส้นทางการทำงานทั้งหมด รวมถึงหลายเมธอด วิธีการนี้มีประสิทธิภาพสำหรับ โปรเซสเซอร์มือถือ ที่มีแคชคำสั่งขนาดเล็ก เนื่องจากร่องรอยที่คอมไพล์แล้วพอดีกับแคช L1
เริ่มตั้งแต่ Android 7.0 Nougat, ART ใช้ JIT แบบเมธอด — มันคอมไพล์เมธอดแต่ละเมธอดตามโปรไฟล์การทำงาน JIT นี้ทำงานเร็วกว่า Dalvik JIT อย่างมาก: เวลาคอมไพล์ทั่วไปสำหรับหนึ่งเมธอดคือ 0.5–1 มิลลิวินาที เทียบกับ 3–5 มิลลิวินาทีใน Dalvik โค้ดที่คอมไพล์แล้วถูกเก็บในพื้นที่หน่วยความจำแยกต่างหาก (แคชโค้ด JIT) แทนที่จะอยู่ในฮีปของแอปพลิเคชัน ซึ่งลดการกระจายตัว
| พารามิเตอร์ | Dalvik JIT | ART JIT |
|---|---|---|
| ประเภท | แบบร่องรอย | แบบเมธอด |
| ความเร็วในการคอมไพล์ | 3–5 มิลลิวินาที/เมธอด | 0.5–1 มิลลิวินาที/เมธอด |
| เกณฑ์การคอมไพล์ | ~200 การเรียก | ไดนามิก |
| แคชโค้ด | ในฮีปแอปพลิเคชัน | แคชโค้ด JIT |
| การทำโปรไฟล์ | ภายใน | ไฟล์ .prof ภายนอก |
กลไกหลักของ JIT คือ การตรวจจับเมธอดร้อน ทุกครั้งที่เรียกเมธอดจะเพิ่มตัวนับภายใน เมื่อตัวนับข้ามเกณฑ์ เมธอดจะถูกทำเครื่องหมายเป็น “ร้อน” และส่งไปคอมไพล์ ใน Dalvik เกณฑ์ถูกกำหนดตายตัว (~200 การเรียก) ใน ART ตัวนับถูกกำหนดแบบไดนามิกขึ้นอยู่กับทรัพยากรที่มีของอุปกรณ์
กระบวนการคอมไพล์ประกอบด้วยหลายขั้นตอน ขั้นแรก — การวิเคราะห์ไบต์โค้ด: JIT ตรวจสอบสตรีมคำสั่งและสร้างกราฟการไหลของข้อมูล ขั้นที่สอง — การเพิ่มประสิทธิภาพ: การอินไลน์เมธอดเล็ก การกำจัดโค้ดที่ตายแล้ว การพับค่าคงที่ ขั้นที่สาม — การสร้างโค้ด: การแปลงกราฟที่ปรับให้เหมาะสมเป็นคำสั่งเครื่องสำหรับสถาปัตยกรรม CPU เฉพาะ (ARM, ARM64, x86)
// การสาธิตการอินไลน์ — JIT จะอินไลน์เนื้อหาของเมธอด
public int inlineExample() {
return square(5);
}
private int square(int x) {
return x * x;
} // JIT จะแทนที่การเรียกด้วย return 5 * 5;
เทคนิคพิเศษของ JIT — การแทนที่บนสแต็ก (OSR) หากเมธอดมีลูปยาวที่ไม่สิ้นสุดเป็นร้อยรอบ JIT สามารถคอมไพล์ลูป “ทันที” และแทนที่เวอร์ชันที่แปลความหมายด้วยเวอร์ชันที่คอมไพล์แล้วระหว่างการทำงาน OSR มีประสิทธิภาพโดยเฉพาะอย่างยิ่งสำหรับงานคำนวณ: การเรนเดอร์, การประมวลผลภาพ, การเข้ารหัส
JIT และ AOT เป็นสองแนวทางการคอมไพล์ที่มีการแลกเปลี่ยนที่ตรงกันข้าม JIT เสียสละความเร็วในการเริ่มต้นครั้งแรกเพื่อขนาดการแจกจ่ายที่กะทัดรัดและความสามารถในการปรับตัว AOT เสียสละเวลาการติดตั้งและพื้นที่ดิสก์เพื่อประสิทธิภาพสูงสุดตั้งแต่วินาทีแรก ไม่มีแนวทางใดที่ดีกว่าอย่างแท้จริง — การเลือกขึ้นอยู่กับสถานการณ์
ข้อได้เปรียบหลักของ JIT คือ การเพิ่มประสิทธิภาพแบบปรับตัว JIT สามารถใช้ข้อมูลโปรไฟล์ที่ไม่พร้อมใช้งานสำหรับ AOT: ชนิดวัตถุที่แน่นอน ความถี่ในการเรียกจริง รูปแบบการแตกแขนงจริง ซึ่งช่วยให้สามารถใช้การเพิ่มประสิทธิภาพเชิงรุกที่เป็นไปไม่ได้กับการคอมไพล์แบบคงที่ ตัวอย่างเช่น JIT สามารถ de-virtualize การเรียกเมธอดหากพบผู้รับเพียงชนิดเดียวในทางปฏิบัติ
| เกณฑ์ | JIT | AOT |
|---|---|---|
| เวลาติดตั้ง | ทันที | ขึ้นอยู่กับขนาด |
| การเริ่มต้นครั้งแรก | ช้ากว่า (อุ่นเครื่อง) | เร็ว |
| พื้นที่ดิสก์ | น้อยที่สุด | +15–30% |
| ความสามารถในการปรับตัว | สูง | ต่ำ |
| การใช้ CPU | พุ่งสูงระหว่างการคอมไพล์ | เสถียร |
การคอมไพล์ JIT เหมาะสมกว่าเมื่อการปรับใช้ที่รวดเร็วและการประหยัดพื้นที่ดิสก์มีความสำคัญ ในบริบทของการพัฒนาโมบายล์ JIT เหมาะสำหรับแอปพลิเคชันที่อัปเดตบ่อยครั้ง (การทดสอบ A/B, การแก้ไขด่วน) JIT ยังสะดวกในระหว่างการพัฒนา เมื่อโค้ดถูกสร้างใหม่หลายสิบครั้งต่อวัน — ทุกวินาทีที่ประหยัดได้ในการคอมไพล์จะเร่งวงจรป้อนกลับ
JIT มอบข้อได้เปรียบเชิงปฏิบัติหลายประการแก่ผู้พัฒนา ข้อแรก — ขนาด APK เล็ก ด้วยแนวทาง JIT มีเพียงไบต์โค้ด (DEX) เท่านั้นที่ถูกบรรจุใน APK ซึ่งใช้พื้นที่น้อยกว่าโค้ดพื้นเมืองที่คอมไพล์แล้ว 20–30% สำหรับผู้ใช้ที่มีพื้นที่จัดเก็บภายในจำกัด นี่เป็นข้อได้เปรียบที่สำคัญ
ข้อได้เปรียบที่สองคือ การปรับตัวให้เข้ากับอุปกรณ์ JIT คอมไพล์โค้ดโดยคำนึงถึงสถาปัตยกรรม CPU จริง, ขนาด RAM และโหลดปัจจุบัน ตัวอย่างเช่น บนอุปกรณ์ที่มี RAM 2 GB, JIT อาจคอมไพล์อย่างไม่รุนแรงเพื่อประหยัดหน่วยความจำ ในขณะที่บนเรือธงที่มี 12 GB สามารถใช้การเพิ่มประสิทธิภาพทั้งหมดที่เป็นไปได้ การคอมไพล์ AOT ในทางกลับกัน จะกำหนดการตัดสินใจ ณ เวลาที่ติดตั้ง
ไบต์โค้ด ยังคงเป็นอิสระจากแพลตฟอร์ม ซึ่งทำให้การแจกจ่ายแอปพลิเคชันง่ายขึ้น APK หนึ่งทำงานบนอุปกรณ์ ARM, ARM64 และ x86 และ JIT สร้างโค้ดพื้นเมืองสำหรับแต่ละสถาปัตยกรรม แนวทาง AOT จะต้องรวมโค้ดพื้นเมืองหลายรูปแบบใน APK (เพิ่มขนาด) หรือคอมไพล์เวอร์ชันแยกต่างหากสำหรับแต่ละสถาปัตยกรรม
ข้อเสียหลักของ JIT คือ ความล่าช้าในการอุ่นเครื่อง ผู้ใช้พบกับความช้าในวินาทีแรกของแอปพลิเคชันขณะที่ JIT คอมไพล์เมธอดร้อน ในเกม สิ่งนี้ปรากฏเป็นการกระตุกในระดับเริ่มต้น ในแอปพลิเคชันที่มีแอนิเมชัน — การเปลี่ยนระหว่างหน้าจอครั้งแรกที่สะดุด
ข้อเสียที่สอง — การใช้พลังงาน กระบวนการคอมไพล์ทำให้ CPU ทำงานหนัก เพิ่มการใช้พลังงาน 10–20% ในช่วงอุ่นเครื่อง บนอุปกรณ์ที่ใช้แบตเตอรี่ สิ่งนี้ลดอายุการใช้งานแบตเตอรี่ โดยเฉพาะอย่างยิ่งในสถานการณ์ที่มีการรีสตาร์ทแอปพลิเคชันบ่อยครั้ง (การทำงานหลายอย่างพร้อมกันด้วยหน่วยความจำจำกัด ที่ระบบยกเลิกการโหลดและโหลดกระบวนการใหม่)
อีกปัญหาหนึ่ง — การกระจายตัวของแคช JIT โค้ดที่คอมไพล์แล้วถูกเก็บในพื้นที่หน่วยความจำต่อเนื่อง เมื่อคลาสใหม่ถูกโหลดและเมธอดเพิ่มเติมถูกคอมไพล์ แคชจะกระจายตัว เพิ่มค่าใช้จ่ายในการจัดการหน่วยความจำ ใน Dalvik ปัญหานี้แก้ไขได้ด้วยการล้างแคชเป็นระยะ ใน ART แคช JIT ถูกจัดสรรแยกจากฮีปและใช้กลยุทธ์การจัดเรียงข้อมูลของตัวเอง
แนวทางที่ทันสมัยใน ART — การคอมไพล์แบบไฮบริด ผสานจุดแข็งของ JIT และ AOT ระหว่างการติดตั้งแอปพลิเคชัน ไม่มีการคอมไพล์ใด ๆ — มีเพียงการตรวจสอบไบต์โค้ด (verify) ซึ่งรับประกันการติดตั้งที่รวดเร็วและการใช้พื้นที่น้อยที่สุด การเริ่มต้นครั้งแรกทำงานในโหมดแปลความหมายด้วยการคอมไพล์ JIT ของเมธอดร้อน — ผู้ใช้ได้รับประสิทธิภาพที่ยอมรับได้โดยไม่ต้องรอนาน
พร้อมกันนั้น โปรไฟล์เลอร์ในพื้นหลัง จะรวบรวมข้อมูลเกี่ยวกับการใช้งานจริง หลังจาก 2–3 การเริ่มต้นแอปพลิเคชันเต็มรูปแบบ โปรไฟล์จะมีความสมบูรณ์เพียงพอ และระบบจะเรียกใช้ dex2oat เพื่อคอมไพล์เมธอดร้อนเป็นโค้ดพื้นเมือง การดำเนินการนี้จะดำเนินการในพื้นหลังเมื่ออุปกรณ์ไม่ได้รับภาระ (กำลังชาร์จ, หน้าจอปิด) หลังจาก AOT ในพื้นหลังเสร็จสมบูรณ์ แอปพลิเคชันจะมีประสิทธิภาพเทียบเท่ากับการคอมไพล์ AOT เต็มรูปแบบ
# บังคับเริ่มการคอมไพล์ในพื้นหลัง
adb shell cmd package compile -m speed-profile -f com.example.app
# ดูสถานะการคอมไพล์
adb shell cmd package dump-profiles com.example.app
ตาม Google I/O 2017 การคอมไพล์แบบไฮบริดลดเวลาการติดตั้งแอปพลิเคชันลง 30–50% เมื่อเทียบกับ AOT บริสุทธิ์ พื้นที่ดิสก์ที่ถูกครอบครองบนพาร์ติชันระบบลดลง 20–30% ในขณะเดียวกัน ประสิทธิภาพหลังการคอมไพล์ในพื้นหลังเทียบเท่ากับระดับของ AOT เต็ม สถานการณ์เดียวที่ไฮบริดด้อยกว่า AOT คือการเริ่มต้นครั้งแรกทันทีหลังการติดตั้ง: แอปพลิเคชันทำงานในโหมด JIT และอาจช้ากว่า 10–15%
คำถามที่พบบ่อย
JIT เป็นวิธีเพิ่มความเร็วโปรแกรมที่โค้ดถูกแปลเป็นภาษาเครื่องไม่ใช่ล่วงหน้า แต่เป็นส่วน ๆ ระหว่างการทำงาน ส่วนที่บ่อยที่สุดถูกคอมไพล์และเก็บในแคช ในขณะที่ส่วนที่หายากยังคงอยู่ในรูปแบบเดิม
JIT คอมไพล์โค้ดระหว่างการทำงาน ประหยัดพื้นที่และเร่งการติดตั้ง AOT คอมไพล์โค้ดทั้งหมดล่วงหน้า — แอปพลิเคชันเริ่มต้นเร็วขึ้นแต่ต้องการพื้นที่ดิสก์และเวลาติดตั้งมากกว่า
JIT ไม่ได้ถูกลบ — มัน วิวัฒนาการ ใน Android 5.0, Dalvik ที่มี JIT ถูกแทนที่ด้วย ART ที่มี AOT บริสุทธิ์ ใน Android 7.0, JIT กลับมาสู่ ART ในฐานะส่วนหนึ่งของระบบไฮบริดที่ทำงานร่วมกับการคอมไพล์ AOT ในพื้นหลังเพื่อประสิทธิภาพที่เหมาะสมที่สุด
JIT เพิ่มการใช้พลังงาน 10–20% ในช่วงอุ่นเครื่องเนื่องจากโหลดของ CPU หลังจากคอมไพล์เมธอดร้อนเสร็จ การใช้พลังงานจะกลับสู่ระดับปกติ โหมดไฮบริดของ ART ลดการพุ่งสูงเหล่านี้ผ่านการคอมไพล์ในพื้นหลัง
ใช่ ในสถานการณ์ที่มีการคำนวณเข้มข้น ผู้ใช้อาจสังเกตเห็น ความช้า ในวินาทีแรกของการทำงานของแอปพลิเคชันหรือตอนเริ่มเกม ใน Android เวอร์ชันสมัยใหม่ (8.0+) โหมดไฮบริดลดผลกระทบนี้ด้วยการคอมไพล์แบบมีโปรไฟล์
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม