JIT บน PHP8 คืออะไร และมันแตกต่างจากการไม่ใช้อย่างไร
JIT ย่อมาจาก Just In Time ถ้าแปลแบบภาษาบ้านๆจะได้ประมาณว่า "ทันเวลา" แต่ถ้าแปลตามวิกีพีเดียจะได้ว่า "ระบบทันเวลาพอดี" ซึ่งหมายถึงการทำให้ระบบสามารถประมวลผลได้ทันเวลาตามที่ร้องขอ (แบบทันทีทันใด) โดยไม่ต้องรอ
PHP เป็น ภาษาสคริปต์ที่อาศัยการแปลทีละบรรทัดเพื่อประมวลผล JIT ใช้เทคนิคการแปลง PHP Opcode ที่ได้ทำการแคชไว้ชั้นหนึ่งแล้วโดย OPcache ให้เป็นโค้ดที่สามารถประมวผลได้เลย (ประมาณภาษาเครื่อง) ทำให้สามารถทำงานได้เร็วขึ้นไม่ต้องมาแปลทุกครั้งเมื่อมีการเรียกใช้เหมือน PHP ปกติ
PHP8 มาพร้อมกับ JIT สองโหมดคือ Tracing JIT (แปลงทั้ง Application) และ Function JIT (แปลงเป็นรายฟังก์ชั่น) ซึ่งค่าเริ่มต้นของ PHP8 คือ Tracing JIT ซึ่งจะทำงานได้เร็วกว่า Function JIT
หลายๆภาษาก็มีการนำ JIT Compiler มาใช้อยู่แล้ว แต่ใน PHP8 เป็นครั้งแรกที่มีการรวมเอาความสามารถนี้เข้ามาในตัวภาษาเลย แต่ในตอนเริ่มต้น PHP8 ไม่ได้เปิดความสามารถนี้ไว้ ต้องมาเปิดใช้เอาเอง
วิธีการเปิดใช้งาน ทำได้โดยการเปิด PHP.INI
อันดับแรกเราต้องเปิดใช้งาน OPcache ก่อน โดยมองหาบรรทัดนี้
เอาเครื่องหมาย ; ด้านหน้าบรรทัดที่ผมทำสีแดงไว้ออก เพื่อกำหนดค่าบรรทัดนั้นๆ จากนั้นเปิดใช้งาน JIT ภายใต้ section opcache (ไม่พบบรรทัดดังกล่าวสามารถเพิ่มได้)
มี JIT และ ไม่มี JIT แตกต่างกันอย่างไร
คงเป็นคำถามที่หลายๆคนอยากรู้ ก่อนอื่นอธิบายก่อน ว่า JIT ทำงานอย่างไร
JIT จะอาศัยการตรวจสอบว่าโค้ดส่วนใดที่ถูกเรียกใช้บ่อยๆบ้าง หากพบว่าโค้ดส่วนใดถูกเรียกใช้ซ้ำ JIT จะทำการแปลงโค้ดชุดนั้นไว้ให้เพื่อให้การเรียกใช้ในครั้งต่อๆไปสามารถทำได้ทันที ไม่ต้องแปลงโค้ดชุดนั้นในทุกๆครั้งที่มีการเรียกใช้ (ส่วนตัวมองว่าการทำแบบนี้เป็นเรื่องยากอยู่เหมือนกัน ยากกว่าการแปลงโค้ดทั้งหมดให้เป็นภาษาเครื่องซะอีก เนื่องจากต้องคอยประเมินว่าโค้ดส่วนใดควรถูกแคช และหากมันพบว่าโค้ดไม่จำเป็นต้องแคช หรือไม่เหมาะที่จะแคช JIT อาจไม่มีประโยชน์เลย)
มาดูการทดสอบทั้งกรณีที่เปิดการใช้งาน JIT และ ไม่เปิดใช้งานกัน
ดูที่โค้ด PHP ที่ผมใช้ทดสอบกันก่อน
โค้ดตัวอย่างเป็นการเรียงลำดับแบบ bubble sort ผลการทดสอบเป็นไปตามกราฟ ซึ่งจะเห็นว่าเมื่อเปิดใช้งาน JIT เวลาที่ใช้ในการประมวลผลลดลงไปเท่าตัวเลยทีเดียว ข้อสังเกต
PHP เป็น ภาษาสคริปต์ที่อาศัยการแปลทีละบรรทัดเพื่อประมวลผล JIT ใช้เทคนิคการแปลง PHP Opcode ที่ได้ทำการแคชไว้ชั้นหนึ่งแล้วโดย OPcache ให้เป็นโค้ดที่สามารถประมวผลได้เลย (ประมาณภาษาเครื่อง) ทำให้สามารถทำงานได้เร็วขึ้นไม่ต้องมาแปลทุกครั้งเมื่อมีการเรียกใช้เหมือน PHP ปกติ
PHP8 มาพร้อมกับ JIT สองโหมดคือ Tracing JIT (แปลงทั้ง Application) และ Function JIT (แปลงเป็นรายฟังก์ชั่น) ซึ่งค่าเริ่มต้นของ PHP8 คือ Tracing JIT ซึ่งจะทำงานได้เร็วกว่า Function JIT
หลายๆภาษาก็มีการนำ JIT Compiler มาใช้อยู่แล้ว แต่ใน PHP8 เป็นครั้งแรกที่มีการรวมเอาความสามารถนี้เข้ามาในตัวภาษาเลย แต่ในตอนเริ่มต้น PHP8 ไม่ได้เปิดความสามารถนี้ไว้ ต้องมาเปิดใช้เอาเอง
วิธีการเปิดใช้งาน ทำได้โดยการเปิด PHP.INI
อันดับแรกเราต้องเปิดใช้งาน OPcache ก่อน โดยมองหาบรรทัดนี้
zend_extension=opcache
............
[opcache]
; Determines if Zend OPCache is enabled
opcache.enable=1
เอาเครื่องหมาย ; ด้านหน้าบรรทัดที่ผมทำสีแดงไว้ออก เพื่อกำหนดค่าบรรทัดนั้นๆ จากนั้นเปิดใช้งาน JIT ภายใต้ section opcache (ไม่พบบรรทัดดังกล่าวสามารถเพิ่มได้)
[opcache]
; Determines if Zend OPCache is enabled
opcache.enable=1
opcache.jit_buffer_size=100M
opcache.jit=1255
- opcache.jit_buffer_size ค่าเริ่มต้นคือ 0 หมายถึงปิดใช้งาน JIT ถ้าต้องการเปิดใช้งานให้ระบุปริมาณหน่วยความจำที่ต้องการ หน่วยเป็น K หรือ M
- opcache.jit ค่าเริ่มต้นคือ 1205 (คู่มือแนะนำ 1235 เป็นค่าที่ดีที่สุด แต่หลายๆที่แนะนำ 1255)
print_r(opcache_get_status()['jit']);
มี JIT และ ไม่มี JIT แตกต่างกันอย่างไร
คงเป็นคำถามที่หลายๆคนอยากรู้ ก่อนอื่นอธิบายก่อน ว่า JIT ทำงานอย่างไร
JIT จะอาศัยการตรวจสอบว่าโค้ดส่วนใดที่ถูกเรียกใช้บ่อยๆบ้าง หากพบว่าโค้ดส่วนใดถูกเรียกใช้ซ้ำ JIT จะทำการแปลงโค้ดชุดนั้นไว้ให้เพื่อให้การเรียกใช้ในครั้งต่อๆไปสามารถทำได้ทันที ไม่ต้องแปลงโค้ดชุดนั้นในทุกๆครั้งที่มีการเรียกใช้ (ส่วนตัวมองว่าการทำแบบนี้เป็นเรื่องยากอยู่เหมือนกัน ยากกว่าการแปลงโค้ดทั้งหมดให้เป็นภาษาเครื่องซะอีก เนื่องจากต้องคอยประเมินว่าโค้ดส่วนใดควรถูกแคช และหากมันพบว่าโค้ดไม่จำเป็นต้องแคช หรือไม่เหมาะที่จะแคช JIT อาจไม่มีประโยชน์เลย)
มาดูการทดสอบทั้งกรณีที่เปิดการใช้งาน JIT และ ไม่เปิดใช้งานกัน
ดูที่โค้ด PHP ที่ผมใช้ทดสอบกันก่อน
<?php
$starttime = microtime(true);
$array = array(3,4,1,3,5,1,92,2,4124,424,52,12);
for ($c=0;$c<100000;$c++) {
for ($i=0;$i<count($array);$i++) {
for ($y=0;$y<count($array)-1;$y++) {
if ($array[$y+1] < $array[$y]) {
$t = $array[$y];
$array[$y] = $array[$y+1];
$array[$y+1] = $t;
}
}
}
}
echo 'Hello World! '.floor((microtime(true) - $starttime) * 1000);
โค้ดตัวอย่างเป็นการเรียงลำดับแบบ bubble sort ผลการทดสอบเป็นไปตามกราฟ ซึ่งจะเห็นว่าเมื่อเปิดใช้งาน JIT เวลาที่ใช้ในการประมวลผลลดลงไปเท่าตัวเลยทีเดียว ข้อสังเกต
- ในกรณีใช้งานจริง พบว่าสำหรับโค้ดทั่วๆไป เช่น CMS JIT มีผลในการเพิ่มประสิทธิภาพน้อยมาก เนื่องจากโค้ดในหลายๆจุดอาจไม่ได้เกิดการเรียกใช้ซ้ำบ่อยๆ จนเกิดการแคชได้ ซึ่งผมมองว่าการที่จะทำให้ JIT เกิดประโยชน์ได้ น่าจะเป็นพวกงานประมวลผลหนักๆ เช่นงานคำนวณ หรืองาน AI มากกว่า อันนี้ก็ต้องรอดูกันต่อไปละครับ ว่า PHP จะพัฒนา JIT ไปในทางไหนและอย่างไร
- มีคำถามเพิ่มเติมมาว่า PHP8 มีประสิทธิภาพแตกต่างจาก PHP7 แค่ไหน ผมตอบฟันธงเลยว่าแทบจะไม่เปลี่ยนเลย เฉพาะกับ PHP7.4 นะครับ เนื่องจากการปรับปรุงที่พอทำได้ใน PHP7.4 มาถึงจุดสูงสุดแล้ว ซึ่งเป็นเหตุผลว่าทำไมผู้พัฒนาถึงได้มาพัฒนา JIT แทน