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. •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
  2. 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
  3. 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
  4. 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
  5. 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?
  6. 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.
  7. The Worst Hello World, Ever <?php class Greeting { public

    function sayHello($to) { echo "Hello $to"; } } ! $greeter = new Greeting(); $greeter->sayHello("World"); ?>
  8. 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 ?>
  9. 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"
  10. 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'
  11. 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
  12. VLD — Vulcan Logic Dumper • Outputs, in order: •

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

    name: sayHello Header compiled vars: !0 = $to number of ops: 8
  14. 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 ----------------------------------------------------------------
  15. 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
  16. 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"; } }
  17. 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
  18. 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");
  19. 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
  20. 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
  21. 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
  22. 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;
  23. 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
  24. 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
  25. 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)
  26. 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
  27. 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).
  28. 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++;
  29. 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
  30. 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;
  31. Incrementing By One • ASSIGN: We assign the value (1)

    to the CV !0 • PRE_INC: We increment the CV !0 !52
  32. 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;
  33. 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
  34. 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;
  35. Incrementing By One • ASSIGN: We assign the value (1)

    to the CV !0 • ASSIGN_ADD: We add 1 to the CV (!0) !56
  36. 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';
  37. 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
  38. 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";
  39. 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
  40. 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
  41. 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";
  42. 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
  43. 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";
  44. Assign-Concat • ASSIGN: We assign the value (foo) to the

    CV !0 • ASSIGN_CONCAT: We concat the value “ bar” to the CV !0 !67
  45. 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()
  46. 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)
  47. Function Call with Arguments • SEND_VAL: Queue up two arguments

    to send to the function • DO_FCALL: Call the function !71
  48. 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
  49. 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
  50. 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
  51. 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();
  52. 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