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

Fee39f0c0ffb29d9ac21607ed188be6b?s=128

Davey Shafik

January 25, 2014
Tweet

Transcript

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

  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
  3. About These Slides

  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
  5. What is Profiling?

  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
  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
  8. Benchmark Profile Make Changes

  9. When Should I Profile My Code?

  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?
  11. Common Causes of Slowdowns

  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.
  13. You’re In Trouble No magic bullet

  14. Internals 101

  15. Execution Life-Cycle

  16. None
  17. Execution Lifecycle with an OpCode Cache

  18. None
  19. Tokenizing

  20. The Worst Hello World, Ever <?php class Greeting { public

    function sayHello($to) { echo "Hello $to"; } } ! $greeter = new Greeting(); $greeter->sayHello("World"); ?>
  21. Token Name Value T_OPEN_TAG <?php 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 ?>
  22. 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"
  23. 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'
  24. OpCodes

  25. VLD Vulcan Logic Dumper

  26. 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 <file> !26
  27. VLD — Vulcan Logic Dumper • Outputs, in order: •

    global code (main script) • global functions • class functions !27
  28. Understanding VLD Dumps Class Greeting: Function sayhello: filename: ./Greeting.php function

    name: sayHello Header compiled vars: !0 = $to number of ops: 8
  29. 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 ----------------------------------------------------------------
  30. 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
  31. 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"; } }
  32. 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
  33. 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");
  34. 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
  35. Variables

  36. zval Zend Value

  37. 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
  38. 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
  39. 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;
  40. Strongly Typed

  41. 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
  42. References & Copy-on-Write

  43. 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
  44. 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)
  45. 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
  46. 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).
  47. Optimizing OpCodes

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

  49. 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++;
  50. 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
  51. 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;
  52. Incrementing By One • ASSIGN: We assign the value (1)

    to the CV !0 • PRE_INC: We increment the CV !0 !52
  53. 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;
  54. 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
  55. 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;
  56. Incrementing By One • ASSIGN: We assign the value (1)

    to the CV !0 • ASSIGN_ADD: We add 1 to the CV (!0) !56
  57. Quoting Strings

  58. 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';
  59. 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
  60. 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";
  61. 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
  62. 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
  63. Concatenation

  64. 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";
  65. 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
  66. 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";
  67. Assign-Concat • ASSIGN: We assign the value (foo) to the

    CV !0 • ASSIGN_CONCAT: We concat the value “ bar” to the CV !0 !67
  68. Function/Method Calls

  69. 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()
  70. 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)
  71. Function Call with Arguments • SEND_VAL: Queue up two arguments

    to send to the function • DO_FCALL: Call the function !71
  72. 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
  73. 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
  74. 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
  75. 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();
  76. 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
  77. Live Example

  78. Feedback & Questions: Joind.in: https://joind.in/10286 Twitter: @dshafik Email: davey@engineyard.com

  79. Tokens

  80. OpCodes

  81. VLD In-Depth VLD

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

  83. Magic Methods vs Native Methods __call __callStatic

  84. $i++ vs ++$i vs + 1 vs += 1

  85. Double vs Single Quotes Interpolation vs no-interpolation

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

  87. @ operator

  88. .= vs $string = $string . "foo"

  89. +=, -=, etc

  90. zvals

  91. refcounting & garbage collection