[OpenWest 2014] PHP: Under The Hood

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

Fee39f0c0ffb29d9ac21607ed188be6b?s=128

Davey Shafik

May 09, 2014
Tweet

Transcript

  1. 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
  2. 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
  3. 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?
  4. 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
  5. 22.

    Proprietary and Confidential The Worst “Hello World”, Ever <?php class

    Greeting { public function sayHello($to) { echo "Hello $to"; } } ! $greeter = new Greeting(); $greeter->sayHello("World"); ?>
  6. 23.

    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_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 ?>
  7. 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"
  8. 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'
  9. 27.
  10. 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 <file> VLD - Vulcan Logic Dumper
  11. 30.

    Proprietary and Confidential • Outputs, in order:! • global code

    (main script)! • global functions! • class functions VLD — Vulcan Logic Dumper
  12. 32.

    Proprietary and Confidential Header Understanding VLD Dumps Class Greeting: Function

    sayhello: filename: ./Greeting.php function name: sayHello
  13. 33.

    Proprietary and Confidential Header Understanding VLD Dumps Class Greeting: Function

    sayhello: filename: ./Greeting.php function name: sayHello number of ops: 8
  14. 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
  15. 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 ----------------------------------------------------------------
  16. 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
  17. 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"; } }
  18. 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
  19. 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");
  20. 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.
  21. 41.
  22. 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
  23. 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
  24. 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;
  25. 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
  26. 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
  27. 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)
  28. 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
  29. 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).
  30. 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++;
  31. 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
  32. 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;
  33. 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
  34. 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;
  35. 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
  36. 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;
  37. 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
  38. 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';
  39. 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
  40. 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";
  41. 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
  42. 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
  43. 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";
  44. 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
  45. 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";
  46. 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
  47. 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()
  48. 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)
  49. 77.

    Proprietary and Confidential • SEND_VAL: Queue up two arguments to

    send to the function! • DO_FCALL: Call the function Function Call with Arguments
  50. 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);
  51. 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.)
  52. 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.)
  53. 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();
  54. 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)
  55. 83.

    Proprietary and Confidential Feedback & Questions: ! Feedback: https://joind.in/11172
 Twitter:

    @dshafik Email: davey@engineyard.com Slides: http://daveyshafik.com/slides