PHP OpCode? What's that?

PHP OpCode? What's that?

Every PHP developer heard about OpCode, the magical sauce that runs inside the Zend PHP Engine in order to boost performances.

But what is exactly that OpCode? Why does it make the Zend Engine faster to run your PHP Code? Is it only about performances?

The aim of this talk is to demystify what's going on inside the Zend PHP Engine and to understand what is happening to our PHP code when it's digested by the interpreter and turned into opcode, before being executed on the Zend Virtual Machine.

6648bd4390fba79c9baa6045e58fa337?s=128

Benoit Jacquemont

June 08, 2019
Tweet

Transcript

  1. PHP OpCode PHP OpCode PHP OpCode What's That? What's That?

    What's That? Benoit Jacquemont Benoit Jacquemont Benoit Jacquemont @bjacquemont @bjacquemont @bjacquemont
  2. What Happen To Our Code At Execution Time? <?php $myVar

    = "World"; echo "Hello ".$myVar."!\n";
  3. Lexing/Tokenizing Use the Tokenizer Extension <?php $myVar = "World"; echo

    "Hello ".$myVar."!\n"; Line 1: T_OPEN_TAG ('<?php ') Line 2: T_WHITESPACE (' ') Line 2: T_VARIABLE ('$myVar') Line 2: T_WHITESPACE (' ') string(1) "=" Line 2: T_WHITESPACE (' ') Line 2: T_CONSTANT_ENCAPSED_STRING ('"World"') string(1) ";" Line 2: T_WHITESPACE (' ') Line 3: T_ECHO ('echo') Line 3: T_WHITESPACE (' ') Line 3: T_CONSTANT_ENCAPSED_STRING ('"Hello "') string(1) "." Line 3: T_VARIABLE ('$myVar') string(1) "." Line 3: T_CONSTANT_ENCAPSED_STRING ('"!\n"') string(1) ";" Line 3: T_WHITESPACE (' ')
  4. Parsing To Abstract Syntax Tree <?php $myVar = "World"; echo

    "Hello ".$myVar."!\n";
  5. Compilation: AST To Opcode Real value <?php $myVar = "World";

    echo "Hello ".$myVar."!\n"; L0 (2): ASSIGN CV0($myVar) string("World") L1 (3): T2 = CONCAT string("Hello ") CV0($myVar) L2 (3): T3 = CONCAT T2 string("! ") L3 (3): ECHO T3 L4 (4): RETURN int(1) 0x26 CV0 0x2b46aa8 T2 = 0x08 0x2b49250 CV0 T3 = 0x08 T2 0x2b489f0 0x28 T3 0x3E 0x01
  6. Opcode, Opline And Oparray opcode: instruction to execute opline: instruction

    with operands (0-2) and optional return assignement oparray: sequence of oplines (function, closure, main) CONCAT (0x08) T2 = CONCAT string("Hello ") CV0 ASSIGN CV0 string("World") T2 = CONCAT string("Hello ") CV0 ECHO T2 RETURN int(1)
  7. None
  8. None
  9. None
  10. None
  11. None
  12. None
  13. None
  14. None
  15. Execution Of The Opcode By The Zend VM

  16. None
  17. None
  18. None
  19. None
  20. None
  21. None
  22. None
  23. None
  24. Opcodes Are The Instructions Of The VM Virtual Processor

  25. PHP Version Zend Engine Version Opcodes count 4.x 1 ~130

    5.x 2 167 7.x 3 172 8.x 4 207 Source: Zend/zend_vm_opcodes.h
  26. Why A VM? Reason #1 Performances!

  27. Why A VM? Reason #2 Abstraction Over The Architecture And

    Hardware
  28. Let's Dive Into Let's Dive Into Let's Dive Into Opcode!

    Opcode! Opcode!
  29. How To Display Opcode VLD Extension (from Derick Rethans) (https://pecl.php.net/package/vld)

    php opcache debug (since PHP 7.1) $ php -dvld.active=1 test.php $ php -d opcache.opt_debug_level=0x10000 test.php
  30. Hello World In Opcode! echo "Hello World !"; $_main: L0:

    ECHO string("Hello World!") L1: RETURN int(1)
  31. What About Variables? $msg = "Hello World!"; echo $msg; $_main:

    L0: ASSIGN CV0($msg) string("Hello World!") L1: ECHO CV0($msg) L2: RETURN int(1)
  32. More Complex Variables $a = 1; $b = 2; $result

    = $a + $b + 3; echo $result; $_main: L0: ASSIGN CV0($a) int(1) L1: ASSIGN CV1($b) int(2) L2: T5 = ADD CV0($a) CV1($b) L3: T6 = ADD T5 int(3) L4: ASSIGN CV2($result) T6 L5: ECHO CV2($result) L6: RETURN int(1)
  33. Control Structures

  34. If/Else $test = true; if ($test) { echo "Hello World!";

    } else { echo "Bye World!"; } $_main: L0: ASSIGN CV0($test) bool(true) L1: JMPZ CV0($test) L4 L2: ECHO string("Hello World!") L3: JMP L5 L4: ECHO string("Bye World!") L5: RETURN int(1)
  35. While $test = true; while ($test) { echo "Hello World!";

    $test = false; } $_main: L0: ASSIGN CV0($test) bool(true) L1: JMP L4 L2: ECHO string("Hello World!") L3: ASSIGN CV0($test) bool(false) L4: JMPNZ CV0($test) L2 L5: RETURN int(1)
  36. For for ($i = 0; $i < 10; $i++) {

    echo "Hello World!"; } $_main: L0: ASSIGN CV0($i) int(0) L1: JMP L5 L2: ECHO string("Hello World!") L3: T2 = POST_INC CV0($i) L4: FREE T2 L5: T3 = IS_SMALLER CV0($i) int(10) L6: JMPNZ T3 L2 L7: RETURN int(1)
  37. All Control Structures Can Be Implemented By JMP And IS_*

    Opcodes
  38. Why A VM? Reason #3 Keep PHP expressiveness while really

    executing a simpler language
  39. A Hypothetical Control Structure: Forever New Language Feature, But No

    Need To Change The VM forever { echo "Hello World!"; } $_main: L0: ECHO string("Hello World!") L1: JMP L0
  40. Internal Functions echo microtime(); $_main: L0: INIT_FCALL 0 string("microtime") L1:

    V0 = DO_ICALL L2: ECHO V0 L3: RETURN int(1)
  41. Internal Functions With Parameters $subject = "Such a nice message!";

    str_replace("nice", "cool", $subject); $_main: L0: ASSIGN CV0($subject) string("Such a nice message!") L1: INIT_FCALL 3 string("str_replace") L2: SEND_VAL string("nice") 1 L3: SEND_VAL string("cool") 2 L4: SEND_VAR CV0($subject) 3 L5: DO_ICALL L6: RETURN int(1)
  42. User Function function hello($name) { echo "Hello $name!"; } hello("World");

    $_main: L0: NOP L1: INIT_FCALL 1 string("hello") L2: SEND_VAL string("World") 1 L3: DO_UCALL L4: RETURN int(1) hello: L0: CV0($name) = RECV 1 L1: T1 = CONCAT string("Hello ") CV0($name) L2: T2 = CONCAT T1 string("!") L3: ECHO T2 L4: RETURN null
  43. Inheritance abstract class MyParentClass { function helloWorld() { echo "Hello

    World"; } } class MyChildClass extends MyParentClass{} $object = new MyChildClass(); $object->helloWorld(); $_main: L0: V4 = NEW 0 string("MyChildClass") L1: DO_FCALL L2: ASSIGN CV0($object) V4 L3: INIT_METHOD_CALL 0 CV0($object) string("helloWorld") L4: DO_FCALL L5: RETURN int(1) MyParentClass::helloWorld L0: ECHO string("Hello World") L1: RETURN null
  44. None
  45. None
  46. None
  47. None
  48. None
  49. None
  50. None
  51. None
  52. None
  53. Opcode Cache: Monitoring $status = opcache_get_status(); $status["memory_usage"]["used_memory"] $status["memory_usage"]["free_memory"] $status["opcache_statistics"]["num_cached_keys"] $status["opcache_statistics"]["max_cached_keys"]

    $status["opcache_statistics"]["opcache_hit_rate"]
  54. Opcode Cache: Memory Settings default 64 MB increase when free_memory~0

    opcache.memory_consumption
  55. Opcode Cache: Num Keys Settings default 2000 inc when num_cached_keys~max_cached_keys

    The actual value used will be the rst number in the set of prime numbers {463, 983, 1979, 3907, 7963, 16229, 32531, 65407, 130987} that is greater than or equal to the con gured value. opcache.max_accelerated_files
  56. None
  57. None
  58. The Opcode Optimizer Multipass Static Analysis To Optimize The Generated

    Opcode
  59. Optimizer Before And After Opcodes Before optimizer After optimizer $

    php -d opcache.opt_debug_level=0x10000 test.php $ php -d opcache.opt_debug_level=0x20000 test.php
  60. Optimizer - Before And After Before After if ("foo") {

    echo "Foo!"; } else { echo "Not Foo!"; } L0: JMPZ string("foo") L3 L1: ECHO string("Foo!") L2: JMP L4 L3: ECHO string("Not Foo!") L4: RETURN int(1) L0: ECHO string("Foo!") L1: RETURN int(1)
  61. Optimizer - Before And After Before After for ($i =

    0; $i < 10; $i++) { echo "Hello World!"; } L0: ASSIGN CV0($i) int(0) L1: JMP L5 L2: ECHO string("Hello World!") L3: T2 = POST_INC CV0($i) L4: FREE T2 L5: T3 = IS_SMALLER CV0($i) 10 L6: JMPNZ T3 L2 L7: RETURN int(1) L0: ASSIGN CV0($i) int(0) L1: JMP L4 L2: ECHO string("Hello World!") L3: PRE_INC CV0($i) L4: T1 = IS_SMALLER CV0($i) 10 L5: JMPNZ T1 L2 L6: RETURN int(1)
  62. Optimizer - Performances?

  63. What's Next? What's Next? What's Next?

  64. PHP 8 Just In Time Compilation From opcodes, generating real

    CPU instructions to execute them directly on the hardware
  65. None
  66. None
  67. None
  68. None
  69. PHP 8 JIT - Current State 5x speedup on number

    crunching bench.php, but... ... -0.01% performance on real world applications Main idea: allow PHP to enter areas where it's not used yet github.com/zendtech/php-src/tree/jit-dynasm/
  70. PHP 7.4 Preload The Main Opcache Limitation Opcache caches opcode

    but at le level classes and functions still need to be reloaded into the process memory
  71. PHP 7.4 Preload What It Will Do Permanently load in

    the server memory functions and classes Once loaded, these classes will be available directly, like builtins (\Exception)
  72. PHP 7.4 Preload How It Will Do It At server

    start, runs a PHP script that will load the needed le
  73. PHP 7.4 Preload What Can We Expect From It ZF2

    Hello world app: 2x requests per second If application fully loaded, no need for autoload anymore
  74. Key Takeaways opcodes are the instructions of the Zend virtual

    CPU use opcode cache! monitor your opcode cache check out preload for PHP 7.4 (ETA end of the year)
  75. For More Information Nikita Popov's Blog (nikic.github.io) Julien Pauli's Blog

    (blog.jpauli.tech) Preload RFC PHP 7 Virtual Machine PHP's OPCache extension wiki.php.net/rfc/preload
  76. Thank You! Questions? @bjacquemont www.akeneo.com