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

[phpDay 2014] PHP: Under The Hood

[phpDay 2014] PHP: Under The Hood

We’ve all experienced performance issues and we would typically turn to a profiler. Whether that’s something in userland, or a tool like xdebug or xhprof, the reason is the same: to figure out why our code is slow.

This talk will take that inspection a step further and look under the hood of PHP, at the C internals, for common performance problems.

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

May 16, 2014
Tweet

More Decks by Davey Shafik

Other Decks in Programming

Transcript

  1. PHP: Under the Hood

    View 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,
    and PHP internals
    • Original creator of PHAR/
    PHP_Archive
    • Lead for PHPWomen US
    •@dshafik
    Davey Shafik

    View Slide

  3. How I Got Here

    View Slide

  4. Profiling

    View Slide

  5. 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 Slide

  6. The Performance Loop

    View Slide

  7. Proprietary and Confidential
    The Performance Loop
    Benchmark
    Profile
    Make
    Changes

    View Slide

  8. When Should I Profile My Code?

    View Slide

  9. Do I Have a Problem?

    View Slide

  10. 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 Slide

  11. Common Causes of Slowdowns

    View Slide

  12. 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 Slide

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

    View Slide

  14. Agenda
    Internals 101

    View Slide

  15. Agenda
    Execution Life Cycle

    View Slide

  16. Proprietary and Confidential
    Execution Life Cycle of PHP

    View Slide

  17. Proprietary and Confidential
    Execution Life Cycle of PHP

    View Slide

  18. Proprietary and Confidential
    Execution Lifecycle with an OpCode Cache

    View Slide

  19. Proprietary and Confidential
    Execution Life Cycle of PHP

    View Slide

  20. Proprietary and Confidential
    Execution Life Cycle of PHP

    View Slide

  21. Tokenizing

    View Slide

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

    View Slide

  23. 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 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
    )
    ;
    T_WHITESPACE
    T_CLOSE_TAG ?>

    View Slide

  24. 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_WHIT Hello
    T_VARIABLE $to
    "
    T_CONSTANT_ENCAPSED "World"

    View Slide

  25. Defining a String

    View Slide

  26. 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 Slide

  27. OpCodes

    View Slide

  28. Agenda
    Vulcan Logic Dumper
    VLD

    View Slide

  29. 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 Slide

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

    View Slide

  31. Proprietary and Confidential
    Header
    Understanding VLD Dumps

    View Slide

  32. Proprietary and Confidential
    Header
    Understanding VLD Dumps
    Class Greeting:
    Function sayhello:
    filename: ./Greeting.php
    function name: sayHello

    View Slide

  33. Proprietary and Confidential
    Header
    Understanding VLD Dumps
    Class Greeting:
    Function sayhello:
    filename: ./Greeting.php
    function name: sayHello
    number of ops: 8

    View Slide

  34. 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 Slide

  35. 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 Slide

  36. 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 Slide

  37. 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 Slide

  38. 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 Slide

  39. 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 Slide

  40. 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 Slide

  41. Variables

    View Slide

  42. Agenda
    Zend Value
    zval

    View Slide

  43. 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 Slide

  44. 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 Slide

  45. 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 Slide

  46. Strongly Typed

    View Slide

  47. 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
    Resouce IS_RESOURCE lval

    View Slide

  48. References & Copy-on-Write

    View Slide

  49. 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 Slide

  50. 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 Slide

  51. 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 Slide

  52. 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 Slide

  53. More OpCodes

    View Slide

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

    View Slide

  55. 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 Slide

  56. 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 Slide

  57. 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 Slide

  58. 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 Slide

  59. 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 Slide

  60. 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 Slide

  61. 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 Slide

  62. 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 Slide

  63. Agenda
    Quoting Strings

    View Slide

  64. 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 Slide

  65. 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 Slide

  66. 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 Slide

  67. 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 Slide

  68. 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 Slide

  69. Agenda
    Concatenation

    View Slide

  70. 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 Slide

  71. 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 Slide

  72. 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 Slide

  73. 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 Slide

  74. Function/Method Calls

    View Slide

  75. 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 Slide

  76. 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 Slide

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

    View Slide

  78. 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 Slide

  79. 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 Slide

  80. 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 Slide

  81. 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 Slide

  82. 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 Slide

  83. Proprietary and Confidential
    Feedback & Questions: !
    Feedback: https://joind.in/11293

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

    View Slide