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

[phpAustralia 2015] PHP: Under the Hood

[phpAustralia 2015] PHP: Under the Hood

Performance issues can be caused by many things, from database interactions, web services, disk i/o and, less frequently, the code itself.

We would typically turn to a profiler like xhprof to diagnose these issues, but what if the bottleneck is PHP itself, where do you turn?

This talk will take that inspection a step further and look under the hood of PHP, at the C internals of how things tick.

This talk covers what every PHP developer should know about their tools — like what's really going on when you use double quotes vs single quotes.

If you’ve ever wanted to know exactly what your code is doing, and why ++$i is faster than $i++, this talk is for you.

Davey Shafik

March 12, 2015
Tweet

More Decks by Davey Shafik

Other Decks in Programming

Transcript

  1. PHP: Under the Hood

    View full-size slide

  2. Proprietary and Confidential
    •Community Engineer at Engine
    Yard
    •Author of Zend PHP 5
    Certification Study Guide,
    Sitepoints PHP Anthology: 101
    Essential Tips, Tricks & Hacks &
    PHP Master: Write Cutting Edge
    Code
    •A contributor to Zend Framework
    1 & 2, phpdoc, & PHP internals
    • Original creator of PHAR/
    PHP_Archive
    • Lead for PHPWomen US
    •@dshafik
    Davey Shafik

    View full-size slide

  3. Let’s start a conversation about
    mental health in tech
    mhprompt.org

    View full-size slide

  4. Apologies For the Accent(s)

    View full-size slide

  5. How I Got Here

    View full-size slide

  6. Proprietary and Confidential
    • Profiling Records Relative Speed + Memory Usage + # of calls per
    function + Call graph
    • The act of profiling affects the speed (the outcome) of the code (just like
    quantum physics!)
    • Benchmarking Tests Actual Speed
    • What your users actually see
    Benchmarking Vs Profiling

    View full-size slide

  7. The Performance Loop

    View full-size slide

  8. Proprietary and Confidential
    The Performance Loop

    View full-size slide

  9. When Should I Profile My Code?

    View full-size slide

  10. Do I Have a Problem?

    View full-size slide

  11. Proprietary and Confidential
    • The #1 issue with performance tuning, is assuming you even have a
    problem.
    • Premature Optimization is a waste of time.
    • Determine desired performance (e.g. 100 concurrent users with less than
    1s response times). Benchmark on production hardware. Do you even
    have a problem? How big?
    Do I Have a Problem?

    View full-size slide

  12. Common Causes of Slowdowns

    View full-size slide

  13. Proprietary and Confidential
    • Datastore
    • Doesn’t matter if it’s PostgreSQL, MySQL, Oracle, MongoDB,
    CouchDB, or MSSQL
    • External Resources
    • APIs, Filesystems, Network Sockets, External Processes
    • Bad Code
    • The only great code, is code that never has to run. Everything else, is
    just good code.
    Common Causes of Slowdowns

    View full-size slide

  14. Agenda
    There is no Magic Bullet
    You’re In Trouble

    View full-size slide

  15. Agenda
    Internals 101

    View full-size slide

  16. Agenda
    Execution Life Cycle

    View full-size slide

  17. Proprietary and Confidential
    Execution Life Cycle of PHP

    View full-size slide

  18. Proprietary and Confidential
    Execution Lifecycle with an OpCode Cache

    View full-size slide

  19. Proprietary and Confidential
    Execution Life Cycle of PHP

    View full-size slide

  20. Proprietary and Confidential
    The Worst “Hello World”, Ever
    class Greeting {
    public function sayHello($to) {
    echo "Hello $to";
    }
    }
    $greeter = new Greeting();
    $greeter->sayHello("World");
    ?>

    View full-size slide

  21. Token Name Value
    T_OPEN_TAG T_CLASS class
    T_WHITESPACE
    T_STRING Greeting
    T_WHITESPACE
    {
    T_WHITESPACE
    T_PUBLIC public
    T_WHITESPACE
    T_FUNCTION function
    T_WHITESPACE
    T_STRING sayHello
    (
    T_VARIABLE $to
    )
    T_WHITESPACE
    {
    T_WHITESPACE
    T_ECHO echo
    T_WHITESPACE
    "
    T_ENCAPSED_AND_WHITESP
    ACE
    Hello
    T_VARIABLE $to
    "
    ;
    Token Name Value
    T_WHITESPACE
    }
    T_WHITESPACE
    }
    T_WHITESPACE
    T_VARIABLE $greeter
    T_WHITESPACE
    =
    T_WHITESPACE
    T_NEW new
    T_WHITESPACE
    T_STRING Greeting
    (
    )
    ;
    T_WHITESPACE
    T_VARIABLE $greeter
    T_OBJECT_OPERATOR ->
    T_STRING sayHello
    (
    T_CONSTANT_ENCAPSED_STR
    ING "World"
    )
    ;
    T_WHITESPACE
    T_CLOSE_TAG ?>

    View full-size slide

  22. Proprietary and Confidential
    • Note the difference between interpolated and non-interpolated strings.
    • They are both double quoted strings!
    • Interpolated: 4 Tokens
    • Non-interpolated: 1 Token
    • Start to see why this matters for performance: but don’t get caught up
    by micro-optimizations.
    Tokenization
    "
    T_ENCAPSED_AND_WHITESPACE Hello
    T_VARIABLE $to
    "
    T_CONSTANT_ENCAPSED
    _STRING
    "World"

    View full-size slide

  23. Defining a String

    View full-size slide

  24. echo "bar";
    T_ECHO echo
    T_CONSTANT_ENCAPSED_STRING "bar"
    T_ECHO echo
    "
    T_VARIABLE $a
    T_ENCAPSED_AND_WHITESPACE bar
    "
    T_ECHO echo
    "
    T_CURLY_OPEN {
    T_VARIABLE $a
    }
    T_ENCAPSED_AND_WHITESPACE bar
    "
    echo 'bar';
    echo "$a bar";
    echo "{$a} bar";
    T_ECHO echo
    T_CONSTANT_ENCAPSED_STRING 'bar'

    View full-size slide

  25. Agenda
    Vulcan Logic Dumper
    VLD

    View full-size slide

  26. Proprietary and Confidential
    • An extension to dump the compiled opcodes
    • Installed via PECL

    $ pecl install vld-beta
    • Add the following to php.ini:

    extension=vld.so
    • Use via command line:


    $ php -dvld.active=1 -dvld.execute=0
    VLD - Vulcan Logic Dumper

    View full-size slide

  27. Proprietary and Confidential
    • Outputs, in order:
    • global code (main script)
    • global functions
    • class functions
    VLD — Vulcan Logic Dumper

    View full-size slide

  28. Proprietary and Confidential
    Header
    Understanding VLD Dumps
    Class Greeting:
    Function sayhello:
    filename: ./Greeting.php
    function name: sayHello
    compiled vars: !0 = $to
    number of ops: 8

    View full-size slide

  29. Proprietary and Confidential
    Understanding VLD Output (Cont.)
    • line: The line number in the source file
    • #: The opcode number
    • *: entry (left aligned) and exit points (right aligned), indicated by greater than
    symbols (>)
    • op: The opcode name
    • fetch: Details on global variable fetches (super globals, or the use of the global
    keyword)
    • ext: Extra data associated with the opcode, for example the opcode to which it
    should JMP
    • return: The location where return data from the operation is stored
    • operands: the operands used by the opcode (e.g. two variables to concat)
    line # * op fetch ext return operands
    ----------------------------------------------------------------

    View full-size slide

  30. Proprietary and Confidential
    VLD — Variables
    • Four types of variables
    Exclamation point (!) are compiled variables (CVs) — these are
    pointers to userland variables
    Tilde (~) are temporary variables used for temporary storage
    (TMP_VARs) of in-process operations
    Dollar ($) are another type of temporary variables (VARs) which
    are tied to userland variables like CVs and therefore require
    things like refcounting.
    Colon (:) are temporary variables used for the storage of the
    result of lookups in the class hashtable

    View full-size slide

  31. Proprietary and Confidential
    class Greeting -> sayHello
    Class Greeting:
    Function sayhello:
    number of ops: 5
    compiled vars: !0 = $to
    line # * op fetch ext return operands
    ---------------------------------------------------------------
    3 0 > RECV !0
    4 1 ADD_STRING ~0 'Hello+'
    2 ADD_VAR ~0 ~0, !0
    3 ECHO ~0
    5 4 > RETURN null
    class Greeting {
    public function sayHello($to) {
    echo "Hello $to";
    }
    }

    View full-size slide

  32. Proprietary and Confidential
    class Greeting -> sayHello
    • RECV: The function receives a value which is assigned to !0
    (which represents $to)
    • ADD_STRING: Next we create a temporary variable
    identified by a ~, ~0 and assign the static string,’Hello+',
    where the + represents a space
    • ADD_VAR: After this, we concat the contents our variable, !0
    to our temporary variable, ~0
    • ECHO: Then we echo that temporary variable
    • RETURN: Finally we return null as the function ends

    View full-size slide

  33. Proprietary and Confidential
    Main Script
    number of ops: 9
    compiled vars: !0 = $greeter
    line # * op fetch ext return operands
    ------------------------------------------------------------------
    2 0 > NOP
    8 1 FETCH_CLASS 4 :1 'Greeting'
    2 NEW $2 :1
    3 DO_FCALL_BY_NAME 0
    4 ASSIGN !0, $2
    9 5 INIT_METHOD_CALL !0, 'sayHello'
    6 SEND_VAL 'World'
    7 DO_FCALL_BY_NAME 1
    10 8 > RETURN 1
    $greeter = new Greeting();
    $greeter->sayHello("World");

    View full-size slide

  34. Proprietary and Confidential
    Main Script
    • FETCH_CLASS: First we lookup the class, Greeting; we store this
    reference in :1
    • NEW: Then we instantiate an instance of the class (:1) and assign
    it to a VAR, $2, and
    • DO_FCALL_BY_NAME: call the constructor
    • ASSIGN: Next we assign the resulting object (in VAR $2) to our CV
    (!0)
    • INIT_METHOD_CALL: We start calling the sayHello method, and
    • SEND_VAL: pass in 'World' to the method, and
    • DO_FCALL_BY_NAME: Actually execute the method
    • RETURN: The script ends successfully with an implicit return of 1.

    View full-size slide

  35. Agenda
    Zend Value
    zval

    View full-size slide

  36. Proprietary and Confidential
    • C data structure
    • Used to store all PHP variable ($foo) data
    • Probably the most important thing in PHP
    typedef struct _zval_struct {
    zvalue_value value;
    zend_uint refcount__gc;
    zend_uchar type;
    zend_uchar is_ref__gc;
    } zval;
    zval

    View full-size slide

  37. Proprietary and Confidential
    zval
    member name
    value Stores the actual data
    refcount__gc Number of references to the zval
    type Stores the data type
    is_ref__gc Whether the variable is a reference

    View full-size slide

  38. Proprietary and Confidential
    • C Union
    • Stores only one piece of data
    • Memory is as large as it’s largest member
    • Stores the actual data
    • Strongly Typed
    zval value
    typedef union _zvalue_value {
    long lval;
    double dval;
    struct {
    char *val;
    int len;
    } str;
    HashTable *ht;
    zend_object_value obj;
    } zvalue_value;

    View full-size slide

  39. Strongly Typed

    View full-size slide

  40. Proprietary and Confidential
    • At the C-level, values are strongly typed
    • PHP coerces the data automatically for things like comparisons
    • PHP never changes the zvals actual type
    Strongly Typed
    Data Type ZVAL type Value Storage Location
    NULL IS_NULL none
    Integer IS_LONG lval
    Float IS_DOUBLE dval
    String IS_STRING str.*val, str.len
    Array IS_ARRAY *ht
    Object IS_OBJECT obj
    Boolean IS_BOOL lval
    Resource IS_RESOURCE lval

    View full-size slide

  41. References & Copy-on-Write

    View full-size slide

  42. Proprietary and Confidential
    • To avoid copying values, PHP utilizes reference counting.
    • When one variable is assigned to another, it is always as a
    reference until the data is changed.
    • This is different from PHP references using &
    • Easier to show with examples
    References & Copy-on-Write

    View full-size slide

  43. Proprietary and Confidential
    Refcounting
    $a = 1; // $a = zval#1(value=1, refcount=1)
    $b = $a; // $a = $b = zval#1(value=1, refcount=2)
    $c = $b; // $a = $b = $c = zval#1(value=1, refcount=3)
    $a++; // $b = $c = zval#1(value=1, refcount=2)
    // $a = zval#2(value=2, refcount=1)
    unset($b); // $c = zval#1(value=1, refcount=1)
    // $a = zval#2(value=2, refcount=1)
    unset($c); // zval#1 is destroyed, because refcount=0
    // $a = zval#2(value=2, refcount=1)

    View full-size slide

  44. Proprietary and Confidential
    Refcounting & PHP References
    $a = 1; // $a = zval#1(value=1, refcount=1, is_ref=0)
    $b =& $a; // $a = $b = zval#1(value=1, refcount=2, is_ref=1)
    $b++; // $a = $b = zval#1(value=2, refcount=2, is_ref=1)
    // Due to the is_ref=1 PHP directly changes the zval
    // rather than making a copy

    View full-size slide

  45. Proprietary and Confidential
    Refcounting & PHP References
    $a = 1; // $a = zval#1(value=1, refcount=1, is_ref=0)
    $b = $a; // $a = $b = zval#1(value=1, refcount=2, is_ref=0)
    $c = $b // $a = $b = $c = zval#1(value=1, refcount=3, is_ref=0)
    $d =& $c; // $a = $b = zval#1(value=1, refcount=2, is_ref=0)
    // $c = $d = zval#2(value=1, refcount=2, is_ref=1)
    // $d is a reference of $c, but *not* of $a and $b, so
    // the zval needs to be copied here. Now we have the
    // same zval once with is_ref=0 and once with is_ref=1.
    $d++; // $a = $b = zval#1(value=1, refcount=2, is_ref=0)
    // $c = $d = zval#2(value=2, refcount=2, is_ref=1)
    // Because there are two separate zvals $d++ does
    // not modify $a and $b (as expected).

    View full-size slide

  46. More OpCodes

    View full-size slide

  47. Agenda
    More than one way to do it…
    Incrementing By One

    View full-size slide

  48. Proprietary and Confidential
    Incrementing By One: Post-Increment
    number of ops: 4
    compiled vars: !0 = $i
    line # * op fetch ext return operands
    ------------------------------------------------------------------
    2 0 > ASSIGN !0, 1
    4 1 POST_INC ~1 !0
    2 FREE ~1
    3 > RETURN 1
    $i = 1;
    $i++;

    View full-size slide

  49. Proprietary and Confidential
    • ASSIGN: We assign the value (1) to the CV !0
    • POST_INC: We increment the CV !0 and store it in the temporary
    variable, ~1.
    • FREE: Free the temporary variable (~1)
    Incrementing By One: Post Increment

    View full-size slide

  50. Proprietary and Confidential
    Incrementing By One: Pre-Increment
    number of ops: 5
    compiled vars: !0 = $i
    line # * op fetch ext return operands
    -----------------------------------------------------------------
    2 0 > ASSIGN !0, 1
    4 1 PRE_INC !0
    5 2 > RETURN 1
    $i = 1;
    ++$i;

    View full-size slide

  51. Proprietary and Confidential
    • ASSIGN: We assign the value (1) to the CV !0
    • PRE_INC: We increment the CV !0
    Incrementing By One: Pre-Increment

    View full-size slide

  52. Proprietary and Confidential
    Incrementing By One: Re-assignment
    number of ops: 6
    compiled vars: !0 = $i
    line # * op fetch ext return operands
    ------------------------------------------------------------------
    2 0 > ASSIGN !0, 1
    4 1 ADD ~1 !0, 1
    2 ASSIGN !0, ~1
    5 3 > RETURN 1
    $i = 1;
    $i = $i + 1;

    View full-size slide

  53. Proprietary and Confidential
    • ASSIGN: We assign the value (1) to the CV !0
    • ADD: We add the CV (!0) to 1 and assign it to a temporary variable ~1
    • ASSIGN: We assign the temporary variable ~1 to the CV (!0)
    Incrementing By One: Re-Assignment

    View full-size slide

  54. Proprietary and Confidential
    Incrementing By One: Assign-Add
    number of ops: 5
    compiled vars: !0 = $i
    line # * op fetch ext return operands
    ------------------------------------------------------------------
    2 0 > ASSIGN !0, 1
    4 1 ASSIGN_ADD 0 !0, 1
    5 2 > RETURN 1
    $i = 1;
    $i += 1;

    View full-size slide

  55. Proprietary and Confidential
    • ASSIGN: We assign the value (1) to the CV !0
    • ASSIGN_ADD: We add 1 to the CV (!0)
    Incrementing By One: Assign-Add

    View full-size slide

  56. Agenda
    Quoting Strings

    View full-size slide

  57. Proprietary and Confidential
    Quoting Strings: Single Quotes
    number of ops: 2
    compiled vars: none
    line # * op etch ext return operands
    ----------------------------------------------------------
    2 0 ECHO 'bar'
    3 1 > RETURN 1
    echo 'bar';

    View full-size slide

  58. Proprietary and Confidential
    Quoting Strings: Double Quotes
    echo "bar";
    number of ops: 2
    compiled vars: none
    line # * op etch ext return operands
    ----------------------------------------------------------
    2 0 ECHO 'bar'
    3 1 > RETURN 1

    View full-size slide

  59. Proprietary and Confidential
    Quoting Strings: Simple Interpolation
    number of ops: 5
    compiled vars: !0 = $a
    line # * op fetch ext return operands
    ------------------------------------------------------------------
    2 0 ASSIGN !0, 'foo'
    3 1 ADD_VAR ~1 !0
    2 ADD_STRING ~1 ~1, '+bar'
    3 ECHO ~1
    4 4 > RETURN 1
    $a = 'foo';
    echo "$a bar";

    View full-size slide

  60. Proprietary and Confidential
    • ASSIGN: We assign the value ('foo') to the CV !0
    • ADD_VAR: We create a temporary variable (~1) to hold our interpolate
    string, and assign the CV !0 to it
    • ADD_STRING: We append the string '+bar' to the temporary variable ~1
    and assign it back to ~1
    • ECHO: We output the string in our temporary variable ~1
    Quoting Strings: Simple Interpolation

    View full-size slide

  61. Proprietary and Confidential
    Quoting Strings: Complex Interpolation
    $a = 'foo';
    echo "{$a} bar";
    number of ops: 5
    compiled vars: !0 = $a
    line # * op fetch ext return operands
    ------------------------------------------------------------------
    2 0 ASSIGN !0, 'foo'
    3 1 ADD_VAR ~1 !0
    2 ADD_STRING ~1 ~1, '+bar'
    3 ECHO ~1
    4 4 > RETURN 1

    View full-size slide

  62. Agenda
    Concatenation

    View full-size slide

  63. Proprietary and Confidential
    number of ops: 4
    compiled vars: !0 = $a
    line # * op fetch ext return operands
    ------------------------------------------------------------------
    2 0 > ASSIGN !0, 'foo'
    4 1 CONCAT ~1 !0, '+bar'
    2 ASSIGN !0, ~1
    5 3 > RETURN 1
    Simple Concatenation
    $a = "foo";
    $a = $a . " bar";

    View full-size slide

  64. Proprietary and Confidential
    • ASSIGN: We assign the value ('foo') to the CV !0
    • CONCAT: We concatenate '+bar' to the compiled variable !0 and
    assign it to a temporary variable, ~1
    • ASSIGN: We assign the complete string back to the compiled variable !0
    Simple Concatenation

    View full-size slide

  65. Proprietary and Confidential
    number of ops: 5
    compiled vars: !0 = $a
    line # * op fetch ext return operands
    ------------------------------------------------------------------
    2 0 > ASSIGN !0, 'foo'
    1 ASSIGN_CONCAT 0 !0, '+bar'
    5 2 > RETURN 1
    Assign-Concat
    $a = "foo";
    $a .= " bar";

    View full-size slide

  66. Proprietary and Confidential
    • ASSIGN: We assign the value ('foo') to the CV !0
    • ASSIGN_CONCAT: We concat the value "+bar" to the CV !0
    Assign-Concat

    View full-size slide

  67. Function/Method Calls

    View full-size slide

  68. Proprietary and Confidential
    number of ops: 2
    compiled vars: none
    line # * op fetch ext return operands
    ------------------------------------------------------------------
    2 0 > DO_FCALL 0 'phpinfo'
    3 1 > RETURN 1
    Simple Function Call
    phpinfo()

    View full-size slide

  69. Proprietary and Confidential
    number of ops: 4
    compiled vars: none
    line # * op fetch ext return operands
    ------------------------------------------------------------------
    2 0 > SEND_VAL 1
    1 SEND_VAL 3
    2 DO_FCALL 2 'bcadd'
    3 3 > RETURN 1
    Function Call with Arguments
    bcadd(1, 3)

    View full-size slide

  70. Proprietary and Confidential
    • SEND_VAL: Queue up two arguments to send to the function
    • DO_FCALL: Call the function
    Function Call with Arguments

    View full-size slide

  71. Proprietary and Confidential
    number of ops: 5
    compiled vars: none
    line # * op fetch ext return operands
    ------------------------------------------------------------------
    2 0 > NOP
    7 1 SEND_VAL 1
    2 SEND_VAL 3
    3 DO_FCALL 2 'add'
    8 4 > RETURN 1
    Userland Function
    function add($a, $b) {
    $c = $a + $b;
    return $c;
    }
    add(1, 3);

    View full-size slide

  72. Proprietary and Confidential
    Function add:
    number of ops: 6
    compiled vars: !0 = $a, !1 = $b, !2 = $c
    line # * op fetch ext return operands
    ---------------------------------------------------------------
    2 0 > RECV !0
    1 RECV !1
    3 2 ADD ~0 !0, !1
    3 ASSIGN !2, ~0
    4 4 > RETURN !2
    5 5* > RETURN null
    Userland Function (cont.)

    View full-size slide

  73. Proprietary and Confidential
    • RECV: Receive two arguments, assign them to compiled variables !0, !1
    • ADD: Add the two variables, and assign to a temporary variable, ~0
    • ASSIGN: Assign the result to a third compiled variables, !2
    • RETURN: Return the third compiled variable
    Userland Function (cont.)

    View full-size slide

  74. Proprietary and Confidential
    line # * op fetch ext return operands
    --------------------------------------------------------------------
    2 0 > NOP
    7 1 FETCH_CLASS 4 :1 'foo'
    2 NEW $2 :1
    3 DO_FCALL_BY_NAME 0
    4 ASSIGN !0, $2
    8 5 INIT_METHOD_CALL !0, 'bar'
    6 DO_FCALL_BY_NAME 0
    9 7 INIT_METHOD_CALL !0, 'bat'
    8 DO_FCALL_BY_NAME 0
    10 9 > RETURN 1
    Method Overloading (__call)
    class foo {
    public function bar() {}
    public function __call($a, $b) {}
    }
    $foo = new foo;
    $foo->bar();
    $foo->bat();

    View full-size slide

  75. Proprietary and Confidential
    • FETCH_CLASS: We do a class lookup and store the value in a temporary
    variable, :1
    • NEW: We instantiate the class, assigning the object to temporary variable
    $2 (which requires refcounting)
    • DO_FCALL_BY_NAME: We call the constructor
    • ASSIGN: We assign the instantiated object in $2, to CV !0
    • INIT_METHOD_CALL: We start calling the method
    • DO_FCALL_BY_NAME: We call the 'bar' method
    • INIT_METHOD_CALL: We start calling the method
    • DO_FCALL_BY_NAME: We call the 'bat' method
    Method Overloading (__call)

    View full-size slide

  76. Proprietary and Confidential
    Feedback & Questions:
    Feedback: https://joind.in/

    Twitter: @dshafik
    Email: [email protected]
    Slides: http://daveyshafik.com/slides
    13477

    View full-size slide