0Pricing
PHP Academy · บทเรียน

การทำงานของ Zend Engine

ติดตาม PHP ตั้งแต่ซอร์สโค้ดไปจนถึงรหัสปฏิบัติการและการทำงาน

การทำงานของ Zend Engine เป็นบทเรียน PHP Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน PHP Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส PHP Academy มีบทเรียนทั้งหมด 4 บทเรียน

ภายในเอนจิน

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

บทเรียนนี้จะพาไล่ตั้งแต่ซอร์สโค้ด → โทเคน → AST → รหัสคำสั่ง → การประมวลผลโดย VM

ลำดับการประมวลผล

คำขอแต่ละรายการที่เรียกใช้ไฟล์ PHP จะผ่านสี่ระยะดังนี้:

  • การวิเคราะห์คำ — ข้อความซอร์สโค้ด → โทเคน (ตัวสแกนที่สร้างด้วย re2c)
  • การวิเคราะห์ไวยากรณ์ — โทเคน → ต้นไม้นิพจน์เชิงนามธรรม (ไวยากรณ์ Bison)
  • การคอมไพล์ — AST → อาร์เรย์รหัสคำสั่ง
  • การประมวลผล — Zend VM เดินผ่านรหัสคำสั่ง

หากไม่มี OPcache สามระยะแรกจะทำซ้ำในคำขอทุกรายการ

การสร้างโทเคน

ตัววิเคราะห์คำจะแปลงอักขระเป็นโทเคน เช่น T_VARIABLE, T_ECHO, T_STRING PHP เปิดให้เข้าถึงขั้นตอนนี้ผ่าน token_get_all() / PhpToken ซึ่งเป็นสิ่งที่เครื่องมืออย่าง PHP-CS-Fixer ใช้โดยตรง

<?php
$src = '<?php $x = 1 + 2; echo $x;';

foreach (PhpToken::tokenize($src) as $tok) {
    if ($tok->isIgnorable()) continue;   // skip whitespace
    printf("%-12s %s\n", $tok->getTokenName(), trim($tok->text));
}
?>

AST

โทเคนจะถูกวิเคราะห์ไวยากรณ์เป็นต้นไม้ของ Node ซึ่งอาจมี Node การกำหนดค่าที่มีตัวแปรและนิพจน์การดำเนินการแบบไบนารีเป็นโหนดย่อย PHP สร้าง AST นี้ภายในระบบ (และ nikic/php-parser จะสร้างโครงสร้างที่เทียบเท่ากันในพื้นที่ผู้ใช้ สำหรับเครื่องมือวิเคราะห์แบบสแตติกอย่าง PHPStan)

<?php
// Conceptual AST for:  $x = 1 + 2;
//
//   AST_ASSIGN
//   ├── AST_VAR  ($x)
//   └── AST_BINARY_OP (+)
//       ├── 1
//       └── 2
//
// At compile time PHP folds 1 + 2 into a literal 3
// (constant folding) before generating opcodes.
echo "AST drives opcode generation\n";
?>

รหัสคำสั่ง

คอมไพเลอร์จะสร้าง อาร์เรย์รหัสคำสั่ง ซึ่งเป็นรายการรหัสคำสั่งแบบแบน รหัสคำสั่งแต่ละรายการมีหมายเลขรหัสคำสั่ง (เช่น ZEND_ADD, ZEND_ECHO, ZEND_ASSIGN) และมีตัวดำเนินการได้สูงสุดสองตัวพร้อมผลลัพธ์หนึ่งตัว โดยแต่ละตัวอาจเป็นตัวแปรที่คอมไพล์แล้ว (CV), ค่าชั่วคราว (TMP) หรือค่าคงที่

<?php
// Opcodes for:  $x = 1 + 2; echo $x;
//
// line  op            operands           result
//  ---  ------------  -----------------  -------
//   1   ADD           1, 2               ~0
//   1   ASSIGN        $x, ~0
//   1   ECHO          $x
//   1   RETURN        1
//
// ~0 is a temporary; $x is a compiled variable (CV).
echo "op_array is what OPcache stores\n";
?>

ตรวจสอบรหัสคำสั่ง

คุณสามารถแสดงรหัสคำสั่งที่สร้างขึ้นด้วยส่วนขยาย VLD หรือ opcache.opt_debug_level ของ OPcache ซึ่งจะเผยให้เห็นการคำนวณค่าคงที่ล่วงหน้า การตัดโค้ดที่ไม่มีทางทำงาน และวิธีที่ลำดับการควบคุมถูกแปลงเป็นรหัสคำสั่ง JMP/JMPZ

# Dump opcodes with VLD
php -d vld.active=1 -d vld.execute=0 script.php

# Or via OPcache optimizer debug (pre/post optimization)
php -d opcache.opt_debug_level=0x10000 script.php   # before opt
php -d opcache.opt_debug_level=0x20000 script.php   # after opt

ตัวแปรที่คอมไพล์แล้ว (CV)

ตัวแปรภายในไม่ได้ถูกค้นหาผ่านตารางแฮชในโค้ดที่คอมไพล์แล้ว แต่คอมไพเลอร์จะกำหนด ช่อง CV ที่มีหมายเลขให้แต่ละตัว การเข้าถึง $x ครั้งที่สองเป็นการเข้าถึงดัชนีอาร์เรย์ ไม่ใช่การค้นหาในตารางสัญลักษณ์ นี่เป็นเหตุผลสำคัญที่ทำให้ตัวแปรภายในทำงานได้รวดเร็ว

<?php
// Each named local gets a fixed CV slot at compile time:
//   $a -> CV0   $b -> CV1   $sum -> CV2
function add(int $a, int $b): int {
    $sum = $a + $b;   // ADD CV0, CV1 -> CV2
    return $sum;      // RETURN CV2
}
echo add(2, 3) . PHP_EOL;
?>

วงรอบการทำงานของ VM

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

<?php
// if ($n > 0) echo 'pos'; compiles roughly to:
//
//   IS_SMALLER 0, $n  -> ~T
//   JMPZ ~T, ->L1        ; if false, skip
//   ECHO 'pos'
// L1:
//   RETURN 1
//
// The VM follows opline; JMPZ rewrites it conditionally.
$n = 5;
if ($n > 0) echo "pos\n";
?>

บทบาทของ OPcache

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

ต้นทุนการเรียกใช้ฟังก์ชัน

การเรียกใช้ฟังก์ชันจะสร้างเฟรมสแตกใหม่ของ VM โดยใช้รหัสคำสั่ง INIT_FCALL ตามด้วย SEND_VAL/SEND_VAR สำหรับอาร์กิวเมนต์แต่ละตัว และจบด้วย DO_FCALL การเข้าใจขั้นตอนนี้ช่วยอธิบายได้ว่าเหตุใดการเรียกใช้ฟังก์ชันขนาดเล็กจำนวนมากจึงมีต้นทุนที่วัดได้ และเหตุใดการแทรกโค้ดของฟังก์ชันหรือ JIT จึงสำคัญในวงรอบที่ทำงานบ่อย

<?php
// square($x) compiles to a call sequence:
//   INIT_FCALL 'square'
//   SEND_VAR   $x
//   DO_FCALL        -> ~R
//   ASSIGN $y, ~R
function square(int $x): int { return $x * $x; }

$total = 0;
for ($i = 1; $i <= 5; $i++) {
    $total += square($i);   // one call sequence per iteration
}
echo $total . PHP_EOL;     // 1+4+9+16+25 = 55
?>

การปิดคำขอ

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

ตรวจสอบความเข้าใจ

OPcache จัดเก็บอะไรไว้โดยเฉพาะ จึงไม่ต้องคอมไพล์ซ้ำ

สรุป

ลำดับการประมวลผลของ Zend คือ วิเคราะห์คำ → วิเคราะห์ไวยากรณ์ → คอมไพล์ → ประมวลผล ซอร์สโค้ดจะกลายเป็นโทเคน จากนั้นเป็น AST และต่อมาเป็น อาร์เรย์รหัสคำสั่ง ที่วงรอบการประมวลผลของ VM จะนำไปทำงาน โดยตัวแปรภายในจัดเก็บอยู่ในช่อง CV ที่มีหมายเลข และการเรียกใช้ฟังก์ชันจะสร้างเฟรมสแตก OPcache จะแคชอาร์เรย์รหัสคำสั่ง (พร้อมขั้นตอนเพิ่มประสิทธิภาพ) ดังนั้นในแต่ละคำขอจึงทำซ้ำเพียงการประมวลผล

คำถามที่พบบ่อย

บทเรียน “การทำงานของ Zend Engine” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “การทำงานของ Zend Engine” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส PHP Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส PHP Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “การทำงานของ Zend Engine”

ติดตาม PHP ตั้งแต่ซอร์สโค้ดไปจนถึงรหัสปฏิบัติการและการทำงาน คุณปฏิบัติ PHP Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน PHP Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน PHP Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน

บทเรียน “การทำงานของ Zend Engine” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน PHP Academy นี้ได้ไหม

ได้ บทเรียน PHP Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. การทำงานของ Zend Engine
  2. การจัดการหน่วยความจำและการเก็บขยะ
  3. OPcache และการคอมไพล์ JIT
  4. การเขียนส่วนขยาย PHP พื้นฐานด้วย C
← กลับไปที่ PHP Academy