Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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.

Benoit Jacquemont

June 08, 2019
Tweet

More Decks by Benoit Jacquemont

Other Decks in Programming

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

    View Slide

  2. What Happen To Our Code At
    Execution Time?
    $myVar = "World";
    echo "Hello ".$myVar."!\n";

    View Slide

  3. Lexing/Tokenizing
    Use the Tokenizer Extension
    $myVar = "World";
    echo "Hello ".$myVar."!\n";
    Line 1: T_OPEN_TAG ('')
    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 ('
    ')

    View Slide

  4. Parsing To Abstract Syntax Tree
    $myVar = "World";
    echo "Hello ".$myVar."!\n";

    View Slide

  5. Compilation: AST To Opcode
    Real value
    $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

    View Slide

  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)

    View Slide

  7. View Slide

  8. View Slide

  9. View Slide

  10. View Slide

  11. View Slide

  12. View Slide

  13. View Slide

  14. View Slide

  15. Execution Of The Opcode
    By The Zend VM

    View Slide

  16. View Slide

  17. View Slide

  18. View Slide

  19. View Slide

  20. View Slide

  21. View Slide

  22. View Slide

  23. View Slide

  24. Opcodes Are The
    Instructions Of The VM
    Virtual Processor

    View Slide

  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

    View Slide

  26. Why A VM?
    Reason #1
    Performances!

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  30. Hello World In Opcode!
    echo "Hello World !";
    $_main:
    L0: ECHO string("Hello World!")
    L1: RETURN int(1)

    View Slide

  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)

    View Slide

  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)

    View Slide

  33. Control Structures

    View Slide

  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)

    View Slide

  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)

    View Slide

  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)

    View Slide

  37. All Control Structures
    Can Be Implemented By
    JMP And IS_* Opcodes

    View Slide

  38. Why A VM?
    Reason #3
    Keep PHP expressiveness while really executing a
    simpler language

    View Slide

  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

    View Slide

  40. Internal Functions
    echo microtime();
    $_main:
    L0: INIT_FCALL 0 string("microtime")
    L1: V0 = DO_ICALL
    L2: ECHO V0
    L3: RETURN int(1)

    View Slide

  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)

    View Slide

  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

    View Slide

  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

    View Slide

  44. View Slide

  45. View Slide

  46. View Slide

  47. View Slide

  48. View Slide

  49. View Slide

  50. View Slide

  51. View Slide

  52. View Slide

  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"]

    View Slide

  54. Opcode Cache: Memory Settings
    default 64 MB
    increase when free_memory~0
    opcache.memory_consumption

    View Slide

  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

    View Slide

  56. View Slide

  57. View Slide

  58. The Opcode Optimizer
    Multipass Static Analysis To
    Optimize The Generated Opcode

    View Slide

  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

    View Slide

  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)

    View Slide

  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)

    View Slide

  62. Optimizer - Performances?

    View Slide

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

    View Slide

  64. PHP 8 Just In Time
    Compilation
    From opcodes, generating real CPU instructions to
    execute them directly on the hardware

    View Slide

  65. View Slide

  66. View Slide

  67. View Slide

  68. View Slide

  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/

    View Slide

  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

    View Slide

  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)

    View Slide

  72. PHP 7.4 Preload
    How It Will Do It
    At server start, runs a PHP script that will load the
    needed le

    View Slide

  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

    View Slide

  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)

    View Slide

  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

    View Slide

  76. Thank You!
    Questions?
    @bjacquemont
    www.akeneo.com

    View Slide