GORAGOD.com

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 ก่อน โดยมองหาบรรทัดนี้
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)
หลังจากเปิดใช้งานแล้วให้ restart apache หนึ่งครั้งซึ่งเราสามารถตรวจสอบได้ว่า JIT ถูกเปิดใช้งานแล้วหรือไม่โดยดูจาก PHPinfo ถ้าเปิดใช้งานเรียบร้อยจะเห็น JIT On ภายใต้ section OPcache  หรืออีกวิธี ตรวจสอบด้วยคำสั่ง PHP
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 แทน