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

[PHPBenelux] PHP Performance: Under The Hood

[PHPBenelux] PHP Performance: 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

January 25, 2014
Tweet

More Decks by Davey Shafik

Other Decks in Technology

Transcript

  1. PHP Performance
    Under The Hood (or: Here be dragons!)

    View full-size slide

  2. •Community Engineer @ 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,
    phpdoc, FRAPI, PHP internals

    •Original creator of PHP Archives
    (PHAR)

    • @dshafik
    Davey Shafik

    View full-size slide

  3. About These Slides

    View full-size slide

  4. About These Slides
    • Two slides per “slide”
    • Title Slide (for when I’m talking)
    • Details slide (for later)
    • Nobody likes it when you can read the slide just as
    well as the speaker can
    • I like slides that are useful
    !4

    View full-size slide

  5. What is Profiling?

    View full-size slide

  6. Benchmarking Vs Profiling
    • 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

    View full-size slide

  7. BULLET
    • Bullet point

    • Bullet point

    • Bullet point
    BULLET
    • Bullet point

    • Bullet point

    • Bullet point
    BULLET
    • Bullet point

    • Bullet point

    • Bullet point
    BULLET
    • Bullet point

    • Bullet point

    • Bullet point
    BULLET
    • Bullet point

    • Bullet point

    • Bullet point
    BULLET
    • Bullet point

    • Bullet point

    • Bullet point
    BULLET
    • Bullet point

    • Bullet point

    • Bullet point
    BULLET
    • Bullet point

    • Bullet point

    • Bullet point
    The Performance
    Loop

    View full-size slide

  8. Benchmark
    Profile
    Make
    Changes

    View full-size slide

  9. When Should I Profile My Code?

    View full-size slide

  10. Do You Have a Problem?
    • 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?

    View full-size slide

  11. Common Causes of Slowdowns

    View full-size slide

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

    View full-size slide

  13. You’re In Trouble
    No magic bullet

    View full-size slide

  14. Internals 101

    View full-size slide

  15. Execution Life-Cycle

    View full-size slide

  16. Execution Lifecycle with an OpCode Cache

    View full-size slide

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

    View full-size slide

  18. 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_WHITESPACE 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_STRING "World"
    )
    ;
    T_WHITESPACE
    T_CLOSE_TAG ?>

    View full-size slide

  19. Tokenizing
    • 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.
    !22
    "
    T_ENCAPSED_AND_WHITESPACE Hello
    T_VARIABLE $to
    "
    T_CONSTANT_ENCAPSED_STRING "World"

    View full-size slide

  20. 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

  21. VLD
    Vulcan Logic Dumper

    View full-size slide

  22. VLD — Vulcan Logic Dumper
    • 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
    !26

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  25. Understanding VLD Dumps (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)
    !29
    line # * op fetch ext return operands
    ----------------------------------------------------------------

    View full-size slide

  26. 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
    !30

    View full-size slide

  27. 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

  28. 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
    !32

    View full-size slide

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

  30. 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.
    !34

    View full-size slide

  31. zval
    Zend Value

    View full-size slide

  32. zval
    • 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;
    !37

    View full-size slide

  33. zval
    !38
    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

  34. zval value
    • C Union
    • Stores the actual data
    • Strongly Typed
    !39
    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

  35. Strongly Typed

    View full-size slide

  36. Strongly Typed
    • At the C-level, values are strongly typed
    • PHP coerces the data automatically for things like
    comparisons
    • PHP never changes the zvals actual type
    !41
    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 full-size slide

  37. References & Copy-on-Write

    View full-size slide

  38. References & Copy-on-Write
    • 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
    !43

    View full-size slide

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

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

  41. 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

  42. Optimizing OpCodes

    View full-size slide

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

    View full-size slide

  44. Incrementing By One
    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

  45. Incrementing By One
    • 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)
    !50

    View full-size slide

  46. Incrementing By One
    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

  47. Incrementing By One
    • ASSIGN: We assign the value (1) to the CV !0
    • PRE_INC: We increment the CV !0
    !52

    View full-size slide

  48. Incrementing By One
    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

  49. Incrementing By One
    • 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)
    !54

    View full-size slide

  50. Incrementing By One
    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

  51. Incrementing By One
    • ASSIGN: We assign the value (1) to the CV !0
    • ASSIGN_ADD: We add 1 to the CV (!0)
    !56

    View full-size slide

  52. Quoting Strings

    View full-size slide

  53. Single Quoted Strings
    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

  54. Double Quoted Strings
    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

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

  56. Incrementing By One
    • 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
    !61

    View full-size slide

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

  58. Concatenation

    View full-size slide

  59. Simple Concatenation
    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
    $a = "foo";
    $a = $a . " bar";

    View full-size slide

  60. Simple Concatenation
    • 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
    !65

    View full-size slide

  61. Assign-Concat
    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
    $a = "foo";
    $a .= " bar";

    View full-size slide

  62. Assign-Concat
    • ASSIGN: We assign the value (foo) to the CV !0
    • ASSIGN_CONCAT: We concat the value “ bar” to the
    CV !0
    !67

    View full-size slide

  63. Function/Method Calls

    View full-size slide

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

    View full-size slide

  65. Function Call with Arguments
    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
    bcadd(1, 3)

    View full-size slide

  66. Function Call with Arguments
    • SEND_VAL: Queue up two arguments to send to the
    function
    • DO_FCALL: Call the function
    !71

    View full-size slide

  67. Userland Function
    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

    View full-size slide

  68. Userland Function (cont.)
    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

    View full-size slide

  69. Userland Function (cont.)
    • 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
    !74

    View full-size slide

  70. Method Overloading (__call)
    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
    class foo {
    public function bar() {}
    public function __call($a, $b) {}
    }
    $foo = new foo;
    $foo->bar();
    $foo->bat();

    View full-size slide

  71. Method Overloading (__call)
    • 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
    !76

    View full-size slide

  72. Live Example

    View full-size slide

  73. Feedback & Questions:
    Joind.in: https://joind.in/10286
    Twitter: @dshafik
    Email: [email protected]

    View full-size slide

  74. VLD
    In-Depth VLD

    View full-size slide

  75. Getters/Setters
    VS __get/__set
    VS no getter/setter

    View full-size slide

  76. Magic Methods vs Native
    Methods
    __call
    __callStatic

    View full-size slide

  77. $i++ vs ++$i vs + 1 vs
    += 1

    View full-size slide

  78. Double vs Single Quotes
    Interpolation vs no-interpolation

    View full-size slide

  79. Function Calls (Aliasing)
    Alias the one you call the most

    View full-size slide

  80. .= vs $string = $string .
    "foo"

    View full-size slide

  81. refcounting & garbage
    collection

    View full-size slide