Showing posts with label zend. Show all posts
Showing posts with label zend. Show all posts

Jan 19, 2008

Understanding Opcodes

A blog reader (I have readers???) recently shared his wishlist, "I'm trying to figure out how to show the opcodes like you have in your post...". I promised that I'd throw something together, so here it is:


Slow down, wtf is an "Opcode"?

Short answer: It's the compiled form of a PHP script, similar in principle to Java bytecode or .NET's MSIL. For example, say you've got the following bit of PHP script:

<?php
echo "Hello World";
$a = 1 + 1;
echo $a;

PHP (and it's actual compiler/executor component, the Zend Engine) are going to go through a multi-stage process:

  1. Scanning (a.k.a. Lexing) - The human readable source code is turned into tokens.
  2. Parsing - Groups of tokens are collected into simple, meaningful expressions.
  3. Compilation - Expressions are translated into instruction (opcodes)
  4. Execution - Opcode stacks are processed (one opcode at a time) to perform the scripted tasks.
Side note: Opcode caches (like APC), let the engine perform the first three of these steps, then store that compiled form so that the next time a given script is used, it can use the stored version without having to redo those steps only to come to the same result.

Er... okay... can you elaborate a little? What's lexing? I thought superman put him in jail...

That's Lex Luthor you nit-wit! The most expedient way to explain lexing is by example. Take a look at the manual page for token_get_all(), this gem is actually a wrapper around the Zend Engine's own language scanner. Play around with it a bit, and you'll notice that plugging the short script above into it will produce:

Array
(
[0] => Array
(
[0] => 367
[1] => <?php
)
[1] => Array
(
[0] => 316
[1] => echo
)
[2] => Array
(
[0] => 370
[1] =>
)
[3] => Array
(
[0] => 315
[1] => "Hello World"
)
[4] => ;
[5] => Array
(
[0] => 370
[1] =>
)
[6] => =
[7] => Array
(
[0] => 370
[1] =>
)
[8] => Array
(
[0] => 305
[1] => 1
)
[9] => Array
(
[0] => 370
[1] =>
)
[10] => +
[11] => Array
(
[0] => 370
[1] =>
)
[12] => Array
(
[0] => 305
[1] => 1
)
[13] => ;
[14] => Array
(
[0] => 370
[1] =>
)
[15] => Array
(
[0] => 316
[1] => echo
)
[16] => Array
(
[0] => 370
[1] =>
)
[17] => ;
)

In the array returned by token_get_all(), you have two types of tokens: Single character non-label characters are returned as just that. The character that was found in the source file at that point. Everything else, from labels, to language constructs, to multi-character operators (like >>, +=, etc...) are returned as an array containing two elements: The token ID (which corresponds to T_* constants -- e.g. T_ECHO, T_STRING, T_VARIABLE, etc...), and the actual text which that token came from. What the engine actually gets is slightly more detailed than what you see in the output from token_get_all(), but not by much...

Okay, tokenization just breaks the script into bite-size pieces, how does parsing work then?

The first thing the parser does is throw away all whitespace (Unlike some other P* language...). From the reduced set of tokens, the engine looks for irreducible expressions. How many expressions do you see in the example above? Did you say three? WRONG There are three statements, but one of those statements is made of two distinct expressions. In the case of $a = 1 + 1; the first expression is the addition, followed by the assignment to the variable as a second, distinct expression. All together our expression list is:

  1. echo a constant string
  2. add two numbers together
  3. store the result of the prior expression to a variable
  4. echo a variable

Hey! That's starting to sound familiar! Did I see that kind of description before?

Oh, you must mean my post about strings (plug). That's correct, because these expressions are exactly the pieces which go into making up oplines! Given the expression list we've just reached, the resulting opcodes look something like:

  • ZEND_ECHO 'Hello World'
  • ZEND_ADD ~0 1 1
  • ZEND_ASSIGN !0 ~0
  • ZEND_ECHO !0

What happened to $a? What's the difference between ~0 and !0?

Short answer: !0 is $a


So here's the deal.... oplines have five principle parts:

  • Opcode - Numeric identifier which distinguishes what the opline will do. This is what coresponds to ZEND_ECHO, ZEND_ADD, etc...
  • Result Node - Most opcodes perform "non-terminal" actions. That is; after executing there's some result which can be consumed as an input to a later opline. The result node identifies what temporary location to place the result of the operation in.
  • Op1 Node - One of two inputs to the given opcode. An input may be a constant zval, a reference to a previous result node, a simple variable (CV), or in some cases a "special" data element, such as a class definition. Note that an opcode may use both, one, or neither input node. (Some even use more, see ZEND_OPDATA)
  • Op2 Node - Ditto
  • Extended Value - Simple integer value used to differentiate specific behaviors of an overloaded opcode.

So obviously the nodes are the most complicated parts of an opline, here's the important parts of what they look like:

  • op_type - One of IS_CONST, IS_TMP_VAR, IS_VAR, IS_UNUSED, or IS_CV
  • u - A union of the following elements (the one which is used depends on the value of op_type):
    • constant (IS_CONST) - zval value. This node results which you include a literal value in your script, such as the 'Hello World' or 1 values in the example above.
    • var (IS_VAR or IS_TMP_VAR or IS_CV) - Integer value corresponding to a temporary slot in a lookup table used by the engine.

Now let's look at the difference between those optypes, particularly with respect to u.var:

  • IS_TMP_VAR - These ephemeral values are strictly for use by non-assignment non-terminal expressions. They don't support any refcounting because they're guaranteed not to be shared by any other variable. These are denoted in the examples I use on this site (and in VLD output) as tilde characters (~)
  • IS_VAR - Usually the result of a ZEND_FETCH(_DIM|_OBJ)?_(R|W|RW), or one of the assignment opcodes (which are technically non-terminal expressions since they can be used as inputs to other expressions. Since these are tied to real variables, they have to respect reference counting and are passed about at an extra degree of indirection. They're stored in the same table though. These are denoted by the string symbol ($)
  • IS_CV - "CV" stands for "Compiled Variables". These are basicly cached hash lookups for fetching simple variables from the local symbol table. Once a variable is actually looked up at runtime, it's stored at an extra level of indirection in an even faster lookup table using an index into a vector. That's what the number in this node denotes. These types of nodes are distinguished by a bang (!)

Boggle... You...so lost me there...

Yeah, that explanation sort of got away from me didn't it? What can I clear up?


All I really want to know is how to translate some source code into an opcode..list...thingy...

Heh, okay... first off, that "opcode list thingy" is called an op_array, and you can generate those really easily using one of two PECL packages. You can use my parsekit package, which is useful for programmatic analysis of script compilation, but frankly... it's not what you're looking for and there's not much call for scripts analyzing other scripts anyway. I recommend Derick's VLD (Vulcan Logic Disasembler) which is what'll actually generate the kinds of opcode lists you'll see me use in blog posts.


Once you've got it installed (it installs like any other PECL extension), you can run it with a command like the following:

php -d vld.active=1 -d vld.execute=0 -f yourscript.php

Then sit back and watch the opcodes fly! Important note: Using -r with command line code may not work due to a quirk of the way the engire parses files in older versions of PHP (and with older versions of VLD). Be sure to put your script on disk and reference it using -f if -r doesn't work for you.


Holy schnikies! That's a lot of opcodes! How can I tell what they all do?

Take a look at Zend/zend_vm_def.h in your PHP source tree. In here you'll find a meta-definition of every single opcode used by the engine. Side note: It's used as a source for zend_vm_gen.php which generates the actual code file zend_vm_execute.h. How's that for chicken and egg? Every version of PHP since 5.1.0 has required PHP be already built in order to build it!

Jul 26, 2007

Fun with Unicode

I caught a blog entry by Alexey Zakhlestin via Planet-PHP today which asked the question "Why aren't unicode math symbols supported by programming languages?". The obvious answer, of course, is that they work just fine with the symbols they have and there's no need to mess with a good thing (it doesn't help that typing these symbols is a pita on your average terminal).

Being a whimsical sort, I decided that actually implementing his request would be more fun than simply pish-poshing it. I'm not suggesting this be part of PHP6 (I still don't personally think it's a good idea), but it's a fun exercise and good for a conversation starter... Here's just a few of the things I can do now:

<?php
var_dump(¼, ½ ¾);
// float(0.25)
// float(0.5)
// float(0.75)

var_dump(1 ≤ 2, 2 ≯ 3, 5 ≠ 6);
// bool(true)
// bool(true)
// bool(true)

var_dump(3 × 4, 15 ÷ 5);
// int(12)
// int(3)

var_dump(1 « 3);
// int(8)

/* Your font may be too small,
* but that's a skull and crossbones
*/
☠('aka die/exit');

P.S. - Just FYI, I am aware that the patch referenced above has flaws... The problems are fixable, I just didn't bother fixing 'em because this isn't a serious patch anyway.... Best spend that time on something worthwhile...


P.P.S. - Since the original posting of this article, PHP6-Unicode has been... well, essentially scrapped. So the patch itself is no longer relevant.


Index: Zend/zend_language_scanner.l
===================================================================
RCS file: /repository/ZendEngine2/zend_language_scanner.l,v
retrieving revision 1.167
diff -u -p -r1.167 zend_language_scanner.l
--- Zend/zend_language_scanner.l 12 Jul 2007 09:23:48 -0000 1.167
+++ Zend/zend_language_scanner.l 27 Jul 2007 05:05:47 -0000
@@ -402,6 +402,109 @@ ZEND_API int zend_copy_scanner_string(zv
return 1;
}

+/* Used by {LABEL} for converting unicode operator symbols */
+static inline int zend_scan_unicode_operator(zval *zendlval, char *str, zend_uint str_len, UConverter *conv, int *oplen, int have_equal TSRMLS_DC)
+{
+ int ret = 0;
+
+ switch (Z_USTRVAL_P(zendlval)[0]) {
+ case 0x2260: /* NOT EQUAL TO */
+ ret = T_IS_NOT_EQUAL;
+ break;
+
+ case 0x2264: /* LESS-THAN OR EQUAL TO */
+ case 0x226F: /* NOT GREATER-THAN */
+ ret = T_IS_SMALLER_OR_EQUAL;
+ break;
+
+ case 0x2265: /* GREATER-THAN OR EQUAL TO */
+ case 0x226E: /* NOT LESS-THAN */
+ ret = T_IS_GREATER_OR_EQUAL;
+ break;
+
+ case 0x00AB: /* LEFT-POINTING DOUBLE ANGLE QUOTATION MARK */
+ case 0x226A: /* MUCH LESS THAN */
+ ret = have_equal ? T_SL_EQUAL : T_SL;
+ break;
+
+ case 0x00BB: /* RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK */
+ case 0x226B: /* MUCH MORE THAN */
+ ret = have_equal ? T_SR_EQUAL : T_SR;
+ break;
+
+ case 0x2270: /* NEITHER LESS-THAN NOR EQUAL TO */
+ ret = '>';
+ break;
+
+ case 0x2271: /* NEITHER GREATER-THAN NOR EQUAL TO */
+ ret = '<';
+ break;
+
+ case 0x2276: /* LESS-THAN OR GREATER-THAN */
+ case 0x2277: /* GREATER-THAN OR LESS-THAN */
+ ret = T_IS_NOT_EQUAL;
+ break;
+
+ case 0x2278: /* NEITHER LESS-THAN NOR GREATER-THAN */
+ case 0x2279: /* NEITHER GREATER-THAN NOR LESS-THAN */
+ ret = T_IS_EQUAL;
+ break;
+
+ case 0x00D7: /* MULTIPLICATION SIGN */
+ ret = have_equal ? T_MUL_EQUAL : '*';
+ break;
+
+ case 0x00F7: /* DIVISION SIGN */
+ ret = have_equal ? T_DIV_EQUAL : '/';
+ break;
+
+ case 0x00BC: /* VULGAR FRACTION ONE QUARTER */
+ zval_dtor(zendlval);
+ ZVAL_DOUBLE(zendlval, 0.25);
+ ret = T_DNUMBER;
+ break;
+
+ case 0x00BD: /* VULGAR FRACTION ONE HALF */
+ zval_dtor(zendlval);
+ ZVAL_DOUBLE(zendlval, 0.5);
+ ret = T_DNUMBER;
+ break;
+
+ case 0x00BE: /* VULGAR FRACTION THREE QUARTERS */
+ zval_dtor(zendlval);
+ ZVAL_DOUBLE(zendlval, 0.75);
+ ret = T_DNUMBER;
+ break;
+
+ case 0x2620: /* SKULL AND CROSSBONES */
+ ret = T_EXIT;
+ break;
+ }
+
+ if (ret) {
+ /* How much of the input do we need to consume for this codepoint in this character set? */
+ UChar buffer[2], *buffer_ptr = buffer;
+ UErrorCode status = U_ZERO_ERROR;
+ const char *str_ptr = str;
+
+ ucnv_toUnicode(conv, &buffer_ptr, buffer + 1, &str_ptr, str + str_len, NULL, TRUE, &status);
+ if (U_FAILURE(status) && status != U_BUFFER_OVERFLOW_ERROR) {
+ /* Shouldn't happen... */
+ ret = 0;
+ } else {
+ *oplen = (str_ptr - str);
+ }
+ }
+
+ if (ret && ret != T_DNUMBER) {
+ zval_dtor(zendlval);
+ ZVAL_NULL(zendlval);
+ }
+
+
+ return ret;
+}
+
static inline int zend_check_and_normalize_identifier(zval *zendlval)
{
UChar *norm;
@@ -2217,13 +2320,47 @@ HEREDOC_CHARS ("{"*([^$\n\r\\{]|("
return T_ENCAPSED_AND_WHITESPACE;
}

-{LABEL} {
- if (!zend_copy_scanner_string(zendlval, yytext, yyleng, UG(unicode)?IS_UNICODE:IS_STRING, SCNG(output_conv) TSRMLS_CC)) {
- return 0;
+{LABEL}"="? {
+ zend_bool have_equal = 0;
+
+ if (yytext[yyleng-1] == '=') {
+ have_equal = 1;
}
- if (UG(unicode) && !zend_check_and_normalize_identifier(zendlval)) {
+
+ if (!zend_copy_scanner_string(zendlval, yytext, yyleng - have_equal, UG(unicode)?IS_UNICODE:IS_STRING, SCNG(output_conv) TSRMLS_CC)) {
return 0;
}
+
+ if (UG(unicode)) {
+ UChar *norm;
+ int norm_len;
+
+ if (!zend_is_valid_identifier(Z_USTRVAL_P(zendlval), Z_USTRLEN_P(zendlval))) {
+ int oplen = 0, ret = zend_scan_unicode_operator(zendlval, yytext, yyleng, SCNG(output_conv), &oplen, have_equal TSRMLS_CC);
+
+ if (ret) {
+ yyless(oplen + have_equal);
+ return ret;
+ }
+
+ zval_dtor(zendlval);
+ zend_error(E_COMPILE_WARNING, "Invalid identifier syntax: %r", Z_USTRVAL_P(zendlval));
+ return 0;
+ }
+
+ if (zend_normalize_identifier(&norm, &norm_len, Z_USTRVAL_P(zendlval), Z_USTRLEN_P(zendlval), 0) == FAILURE) {
+ zend_error(E_COMPILE_WARNING, "Could not normalize identifier: %r", Z_USTRVAL_P(zendlval));
+ efree(Z_USTRVAL_P(zendlval));
+ return 0;
+ }
+
+ if (norm != Z_USTRVAL_P(zendlval)) {
+ efree(Z_USTRVAL_P(zendlval));
+ ZVAL_UNICODEL(zendlval, norm, norm_len, 0);
+ }
+ }
+ yyless(yyleng - have_equal);
+
return T_STRING;
}

May 19, 2007

create_function() is not your friend, buddy

While browsing through PHP Developer today, I came across this blog entry: "My new best friend" extolling the virtues of create_function(). Let me tell you why create_function() is not my best friend...

First, despite the disclaimer in the mentioned blog entry, it is as bad as its kissing-cousin eval(). Let's take a look at what create_function() actually does by translating it into userland code:

function create_function($args, $code)
{
static $id = 0;
eval("function __lambda_func($args) { $code }");
while (!runkit_function_rename('__lambda_func', "\0lambda_" . (++$id)));
return "\0lambda_$id";
}

I'll let you contemplate on that awhile....You should be noticing the following sets of problems:

  • Prone to critical abuse by user-supplied code
  • Skips opcode cache optimizations

You should also be thinking about the practical issues with it:

  • Code lives inside quoted strings which means awkward escaping of embedded quotes
  • Encourages not using comments (evil)
  • 100% blind to reflection or PHPDoc style documentation generation
  • I'm sure you can come up with a couple more...

"If eval() is the answer, then you're asking the wrong question"

Jan 26, 2007

You're being lied to

If you're among the crowd who have migrated an OOP based application from PHP4 to PHP5, then I'm sure you've heard the expression "Objects are copied by reference by default in PHP5". Whoever told you that, was lying.

Now, to be fair, it's an innocent lie, since objects do behave in a reference-like manner, but references are NOT what they are. Let's start with a simple illustration proving that they aren't references:

<?php
$a = new stdClass;
$b = $a;
$a->foo = 'bar';
var_dump($b);
/* Notice at this point, that $a and $b are,
* indeed sharing the same object instance.
* This is their reference-like behavior at work.
*/

$a = 'baz';
var_dump($b);

/* Notice now, that $b is still that original object.
* Had it been an actual reference with $a,
* it would have changed to a simple string as well.
*/
?>

What's going on here? Well, the answer is easiest to explain by explaining what the underlying structure of objects are. In PHP5, a variable containing an object identifies the instance by storing a simple numeric value. When an action is going to be performed on an object, that numeric value is used with a lookup table to retreive the actual instance. In PHP4, by contrast, a variable containing an array identifies that object by carrying around the actual properties table itself. What this means in practice is that when you assign (not by reference) a PHP5 object to a new variable, that integer handle is copied into the new variable, but it still points at the same instance, because it's still the same number. Assigning a PHP4 object however, means copying all the properties, effectively generating a new instance, since changes to one will not effect the other.

To put this another way, PHP4 objects are basically Arrays with functions associated with them, PHP5 objects are basicly Resources (a la MySQL result handles, or file pointers) again with functions loosely associated to them. Consider the following code in PHP4 (or any version):

<?php
$fp = fopen('foo.txt', 'r');
$otherVar = $fp;
fwrite($fp, "One\n");
fwrite($otherVar, "Two\n");
fclose($fp);

/* This fails, because the file is closed */
fwrite($otherVar, "Three\n");
?>

You'd fully expect data to be written to the same file, as though you'd used $fp everywhere, rather than interchanging the variables right? Well, PHP5 objects are the same. The instance itself isn't duplicated when you assign to a new variable, just the unique identifier.


I'm lying to you also

"Copying" a variable doesn't exactly mean copying. Take the following code block:

<?php
$a = 'foo';
$b = $a;
$a = 'bar';
?>

Now, you know PHP well enough to know that by the end of this code block, the value of $b will still be 'foo'. What you may not know, is that the original copy of 'foo' that was in $a, was never actually duplicated.

To understand what PHP is doing, you need to understand the internal structure of the variable and how it relates to userspace visible variable names ('a' and 'b' in this case). First off, the actual contents of a variable (known as a zval) consists of four parts: type (e.g. NULL, Boolean, Integer, Float, String, Array, Resource, Object), a specific value (e.g. 123, 3.1415926535, etc...), is_ref - a flag indicating if the value is a reference or not, and refcount which tells how many times this value is being shared.

What you think of as a variable (e.g. $x) is actually just a label, that label ('x' in this case) is used as a lookup to find the zval which conatins the actual value. These are just like keys in an associative array, in fact, the mechanisms are identical.

With me so far? Good. Now, when you first create a variable (e.g. $x = 123;, PHP allocates a new zval for it, stores the specific value, and associates the label with the value:

  'x' => zval ( type       => IS_LONG,
value.lval => 123,
is_ref => 0,
refcount => 1 )

So far, refcount is 1 since the zval value is only being referenced by one label. If we now put this value into a full-reference set using $y =& $x;, the same zval is reused. It's simply associated with a new label and it's reference counters are adjusted properly.

  'x' => zval ( type       => IS_LONG,
| value.lval => 123,
| is_ref => 1,
| refcount => 2 )
'y' /

This way, when you later change the value of $x, $y appears to change as well because it's looking at the same internal value. But what if we hadn't done a reference assignment, what if we'd done a normal assignment: $y = $x;, surprisingly, the result would be almost the same.

  'x' => zval ( type       => IS_LONG,
| value.lval => 123,
| is_ref => 0,
| refcount => 2 )
'y' /

Again, the original zval associated with $x is reused, the only difference this time is that is_ref is not set to 1. This is known as a copy-on-write reference set (as opposed to the full-reference set described above). This 0 flag tells the engine that if anyone tries to change this value (regardless of which label they use to reach it), any other references to it should be left alone. Here's what happens if we take that current state and do $x = 456;

  'y' => zval ( type       => IS_LONG,
value.lval => 123,
is_ref => 0,
refcount => 1 )
'x' => zval ( type => IS_LONG,
value.lval => 456,
is_ref => 0,
refcount => 1 )

$x has been disassociated from the original zval (thus dropping its refcount back to 1), and new zval has been created for it.


Why referencing when you don't have to is a bad idea.

Let's consider one more situation, take a look at this code block:

<?php
$a = 'foo';
$b = $a;
$c = &$a;
?>

At the first instruction, a single zval is created, associated to a single label:

  'a' => zval ( type => IS_STRING, value.str.val = 'foo', is_ref = 0, refcount = 1 )

At the second intstruction, that zval is associated to a second label, so far so good:

  'a' => zval ( type          => IS_STRING,
| value.str.val => 'foo',
| is_ref => 0,
| refcount => 2 )
'b' /

At the third intstruction, however, we run into problems. Since this zval is already tied up in a copy-on-write reference set which include $b, that zval can't be simply promoted to is_ref==1. Doing so would drag $b into $a and $c's full-reference set, and that would be wrong. In order to resolve this, the engine is forced to duplicate that zval into two identical copies, from which it can begin to shuffle around reference flags and counts:

  'b' => zval ( type          => IS_STRING,
value.str.val =>'foo',
is_ref => 0,
refcount => 1 )
'a' => zval ( type => IS_STRING,
| value.str.val => 'foo',
| is_ref => 1,
| refcount => 2 )
'c' /

Now you've got two copies of the same literal value, so you're wasting memory for the storage, and processing time required to actually make the duplication. Since a LOT of events lead to copy-on-write uses (including simply passing an argument to a function), this sort of forced duplication actually happens very commonly when you start involving actual references.


The moral of the story

Assigning values by references when you don't need to (in order to later modify the original value through a different label) is NOT a case of you outsmarting the silly engine and gaining speed and performance. It's the opposite, it's you TRYING to outsmart the engine and failing, because the engine is already doing a better job than you think.

How does this reflect on objects? They're not special. They're not different from other variables. They are not pretty snowflakes. In this code block:

<?php
$a = new stdClass;
$b = $a;
?>

The labels are still placed into copy-on-write reference sets. What's important, is that even when a duplication does occur, (A) only that unique integer is copied (which is cheap), and (B) the duplicated integer still points to the same place. Hence you get reference-like behavior, but not an actual reference by default.

Hungry for more? Check out my coverage of the zval.

Jun 18, 2006

How long is a piece of string

Sunday morning I was asked by an IRC regular: "Where does the engine parse quoted strings?". Being a sunday morning, I began to launch into a sermon on the distinction between CONSTANT_ENCAPSED_STRING and the problems which befall a single-pass compiler when you start to introduce interpolation. Not what he asked precisely, but an important component in answering his question. Unfortunately, at the time I was busy watching the Brasil-Australia game so I didn't go into the kind of detail I would have. Now, some 12 hours later, since Angela is off buying toe-socks in Santa Cruz, I'll bore anyone with little enough life to read my blog by explaining the pitfalls of using PHP's string interpolation without using an optimizer.

To start things off, let's take a page from my earlier discourse on Compiled Variables and look at the opcodes generated by a few simple PHP scripts:

<?php
echo "This is a constant string";
?>

Yields the nice, simple opcode:

ECHO            'This is a constant string'

No problem... Exactly what you'd expect... Now let's complicate the expressions a little:

<?php
echo "This is an interpolated $string";
?>

Yields the surprisingly messy instruction set:

INIT STRING  ~0
ADD_STRING ~0 ~0 'This'
ADD_STRING ~0 ~0 ' '
ADD_STRING ~0 ~0 'is'
ADD_STRING ~0 ~0 ' '
ADD_STRING ~0 ~0 'an'
ADD_STRING ~0 ~0 ' '
ADD_STRING ~0 ~0 'interpolated'
ADD_STRING ~0 ~0 ' '
ADD_VAR ~0 ~0 !0
ECHO ~0

Where !0 represents the compiled variable named $string. Looking at these opcodes: INIT_STRING allocates an IS_STRING variable of one byte (to hold the terminating NULL). Then it's realloc'd to five bytes by the first ADD_STRING ('This' plus the terminating NULL). Next it's realloc'd to six bytes in order to add a space, then again to eight bytes for 'is', then nine to add a space, and so on until the temporary string has the contents of the interpolated variable copied into its contents before being used by the echo statement and finally discarded. Now let's rewrite that line to avoid interpolation and use concatenation instead:

<?php
echo "This is a concatenated " . $string;
?>

Which yields the significantly shorter and simpler set of ops:

CONCAT       ~0 'This is a concatenated ' !0
ECHO ~0

A vast improvement already, but this version still creates a temporary IS_STRING variable to hold the combined string contents meaning that data is duplicated when it's being used in a const context anyway. Now let's try out this oft-overlooked use of the echo statement:

<?php
echo "This is a stacked echo " , $string;
?>

Look close, there is a meaningful difference from the last one. This time we're using a comma rather than a dot between the operands. If you don't know what the comma is doing there, ask the manual then check back here. Here's the resulting opcodes:

ECHO            'This is a stacked echo '
ECHO !0

Same number of opcodes, but this time no temporary variables are being created so there's no duplication and no pointless copying (unless of course $string wasn't of type IS_STRING, in which case it does have to be converted for output, but don't get picky now). Think this is bad? Consider the average heredoc string which spans several lines of prepared output embedding perhaps a handful of variables along the way. Here's one of several such blocks found in run-tests.php within the PHP distribution source tree:


<?php
echo <<NO_PCRE_ERROR

+-----------------------------------------------------------+
| ! ERROR ! |
| The test-suite requires that you have pcre extension |
| enabled. To enable this extension either compile your PHP |
| with --with-pcre-regex or if you've compiled pcre as a |
| shared module load it via php.ini. |
+-----------------------------------------------------------+

NO_PCRE_ERROR;
?>

Notice that we're not even embedding variables to be interpolated here, yet does this come out to a simple, single opcode? Nope, because the rules necessary to catch a heredoc's end token demand the same careful examination as double-quoted variable substitution and you wind up (in this case) with SEVENTY-EIGHT opcodes! One INIT_STRING, 76 ADD_STRINGs. and a final ECHO. That means a malloc, 76 reallocs, and a free which will be executed every time that code snippet comes along. Even the original contents take up more memory because they're stored in 76 distinct zval/IS_STRING structures.

Why does this happen? Because there are about a dozen ways that a variable can be hidden inside an interpolated string. Similarly, when looking for a heredoc end-token, the token can be an arbitrary length, containing any of the label characters, and may or may not sit on a line by itself. Put simply, it's too difficult to encompass in one regular expression.

The engine could perform a second-pass during compilation, however the time saved reassembling these strings will typically be about the same amount of time spent actually processing them during runtime (if one assumes that each instance will execute exactly once). Rather than complicate the build process (potentially slowing down overall run-times in the process), the compiler leaves this optimization step to opcode caches which can achieve exponentially greater advantage cleaning up this mess then caching the results and reusing the faster, leaner versions on all subsequent runs.

If you're using APC, you'll find just such an optimizer built in, but not enabled by default. To turn it on, you'll need to set apc.optimization=on in your php.ini. In addition to stitching these run-on opcodes back together, it'll also add run-time speed-ups like pre-resolving persistent constants to their actual values, folding static scalar expressions (like 1 + 1) to their fixed results (e.g. 2), and simpler stuff like avoiding the use of JMP when the target is the next opcode, or boolean casts when the original expression is known to be a boolean value. (It should be noted that these speed-ups also break some of the runtime-manipulation features of runkit, but that was stuff you....probably should have been doing anyway)

Can't use an optimizer because your webhost doesn't know how to set php.ini options? You can still avoid 90% of the INIT_STRING/ADD_STRING dilema by simply using single quotes and concatenation (or commas when dealing with echo statements). It's a simple trick and one which shouldn't harm maintainability too much, but on a large, complicated script, you just might see an extra request or two per second.


Jun 1, 2006

What the heck is TSRMLS_CC, anyway?

If you've ever worked on the PHP internals or built an extension, you've seen this construct floating around here and there, but noone ever talks about it. Those who know what this is typically answer questions from those who don't with "Don't worry about what it is, just use it here here here and here. And if the compiler says you're missing a tsrm_ls, put it there too..." This isn't laziness on the part of the person answering the question (okay, maybe it is a little bit), it's just that the engine goes so far out of its way to simplify what this magic values does, that there's no profit in a new extension developer knowing the mechanics of it. The information is like a cow's opinion, it doesn't matter, it's Moo.


Since I love to listen to myself rattle on about pointless topics (and I havn't blogged much this month), I thought I'd cover this topic and see if anyone manages to stay awake through it. You can blame Lukas, he got me rolled onto planet-php.net...

Glossary

TSRM
Thread Safe Resource Manager - This is an oft overlooked, and seldom if ever discussed layer hiding in the /TSRM directory of your friendly neighborhood PHP source code bundle. By default, the TSRM layer is only enabled when compiling a SAPI which requires it (e.g. apache2-worker). All Win32 builds have this layer enabled enabled regardless of SAPI choice.


ZTS
Zend Thread Ssafety - Often used synonymously with the term TSRM. Specifically, ZTS is the term used by ./configure ( --enable-experimental-zts for PHP4, --enable-maintainer-zts for PHP5), and the name of the #define'd preprocessor token used inside the engine to determine if the TSRM layer is being used.


tsrm_ls
TSRM local storage - This is the actual variable name being passed around inside the TSRMLS_* macros when ZTS is enabled. It acts as a pointer to the start of that thread's independent data storage block which I'll cover in just a minute


TSRMLS_??
A quartet of macros designed to make the differences between ZTS and non-ZTS mode as painless as possible. When ZTS is not enabled, all four of these macros evaluate to nothing. When ZTS is enabled however, they expand out to the following definitions:
  • TSRMLS_C tsrm_ls
  • TSRMLS_D void ***tsrm_ls
  • TSRMLS_CC , tsrm_ls
  • TSRMLS_DC , void ***tsrm_ls

Globals

In any normal C program (just like in PHP) you have two methods of getting data access to the same block of data in two different functions. One method is to pass the value on the parameter stuck like so:

#include 

void output_func(char *message)
{
printf("%s\n", message);
}

int main(int argc, char *argv[])
{
output_func(argv[0]);

return 0;
}

Alternately, you could store the value in a variable up in the global scope and let the function access it there:

#include 

char *message;

void output_func(void)
{
printf("%s\n", message);
}

int main(int argv, char *argv[])
{
message = argv[0];
output_func();

return 0;
}

Both approaches have their merits and drawbacks and typically you'll see some combination of the two used in a real application. Indeed, PHP is covered in global variables from resource type identifiers, to function callback pointers, to request specific information such as the symbol tables used to store userspace variables. Attempting to pass these values around in the parameter stack would be more than unruly, it'd be impossible for an application like PHP where it's often necessary to register callbacks with external libraries which don't support context data.


So common information, like the execution stack, the function and class tables, and extension registries all sit up in the global scope where they can be picked up and used at any point in the application. For single-threaded SAPIs like CLI, Apache1, or even Apache2-prefork, this is perfectly fine. Request specific structures are initialized during the RINIT/Activation phase, and reset back to their original values during the RSHUTDOWN/Deactivation phase in preparation for the next request. A given webserver like Apache1 can serve up multiple pages at once because it spawns multiple processes each in their own process space with their own independant copies of global data.


Now let's introduce threaded webservers like Apache2-worker, or IIS. Under these conditions, only one process space is active at a given time with multiple threads spun off. Each of these threads then act in the same manner as a single-threaded process might; Servicing requests one-at-a-time as dispatched by inbound requests. The trouble starts to brew as two or more threads try to service the a request at the same time. Each thread wants to use the global scope to store its request-specific information, and tries to do so by writing to the same storage space. At the least, this would result in userspace variables declared in one script showing up in another. In practice, it leads to quick and disasterous segfaults and completely unpredictable behavior as memory is double freed or written with conflicting information by separate threads.


Non-Global Globals

The solution is to require the engine, the core, and any extension using global storage to determine how much memory will be used by request-specific data. Then, at the spin-up of each new thread, allocate a chunk of memory for each of these players to store their data into thus giving each thread its own local storage. In order to group all the individual chuncks used by a given thread together, one last vector of pointers is allocated to store the individual sub-structure pointers into. It's the pointer to this vector which is passed around as the tsrm_ls variable by the TSRMLS_* family of macros. To see how this works, let's look at a example extension:


typedef struct _zend_myextension_globals {
int foo;
char *bar;
} zend_myextension_globals;

#ifdef ZTS
int myextension_globals_id;
#else
zend_myextension_globals myextension_globals;
#endif

/* Triggered at the beginning of a thread */
static void php_myextension_globals_ctor(zend_myextension_globals *myext_globals TSRMLS_DC)
{
myext_globals->foo = 0;
myext_globals->bar = NULL;
}

/* Triggered at the end of a thread */
static void php_myextension_globals_dtor(zend_myextension_globals *myext_globals TSRMLS_DC)
{
if (myext_globals->bar) {
efree(myext_globals->bar);
}
}

PHP_MINIT_FUNCTION(myextension)
{
#ifdef ZTS
ts_allocate_id(&myextension_globals_id, sizeof(zend_myextension_globals),
php_myextension_globals_ctor, php_myextension_globals_dtor);
#else
php_myextension_globals_ctor(&myextension_globals TSRMLS_CC);
#endif

return SUCCESS;
}

PHP_MSHUTDOWN_FUNCTION(myextension)
{
#ifndef ZTS
php_myextension_globals_dtor(&myextension_globals TSRMLS_CC);
#endif

return SUCCESS;
}

Here you can see the extension declaring its global requirements to the TSRM layer by stating that it needs sizeof(zend_myextension_globals) bytes of storage, and providing callbacks to use when initializing (or destroying) a given thread's local storage. The value populated into myextension_globals_id represents the offset (common to all threads) into the tsrm_ls vector where the pointer to that thread's local storage can be found. In the event that ZTS is not enabled, the data storage is simply placed into the true global scope and the thread initialization and shutdown routines are called manually during the Module's Startup and Shutdown phases. If you're wondering why TSRMLS_CC was included in the non-ZTS blocks, then I clearly havn't made you fall asleep yet. Those aren't needed there since we know they evaluate to nothing, but it helps encourage good habits to include them anywhere the function's prototype calls for them.


Putting it all together

The final piece of this thread-safe puzzle comes from the question: "How do I access data in these structures?" And the answer to that question comes in the form of another familiar looking macro. Each extension or core component defines, in one of its header files, a macro which looks something like the following:


#ifdef ZTS
# define MYEXTENSION_G(v) \
(((zend_myextension_globals*)(*((void ***)tsrm_ls))[(myextension_globals_id)-1])->v)
#else
# define MYEXTENSION_G(v) (myextension_globals.v)
#endif

Thus, when ZTS is not enabled, this macro simply plucks the right value out of the imediate value in the global scope, otherwise it uses the ID to locate the thread's local storage copy of the structure and derefence the value from there.


Wanna know more, like how to deal with foreign callbacks where tsrm_ls isn't available? Buy my book!

May 24, 2006

Compiled Variables

Last month at php|tek I gave a presentation on "How PHP Ticks" where I covered, among other things the process of compiling source code into opcodes (an intermediate psuedo-language similar to what java calls "bytecode" or what .NET calls "MSIL"). As part of this section of the presentation, I showed one of the more interresting changes between ZE 2.0 (PHP 5.0) and ZE 2.1 (PHP 5.1), namely: How variables are retreived and used in an operation. More specifically, how they provide a small, yet cumulative, speedup to applications in a way that's transparent to the end-user -- One more reason to like PHP 5.1 right?


After listening to Marcus Whitney's interview with Brion Vibber of WikiMedia in which he mentions my presentation and makes reference to this engine change, I realized that I should clarify what this feature is (and more importantly, what it isn't) before any FUD spreads.

What Compiled Variables (CVs) are

First, let's look at the anantomy of an OpArray. Say you have the following simple script:


<?php
$a = 123;
$b = 456;
$c = $a + $b;
echo $c;

Now let's see how ZE 2.0 (PHP5.0) compiles this (ZE1.x/PHP4.x comes out to nearly identical opcodes). The $0 and ~0 references you see are (for lack of a better one sentence explanation) types of temporary variables (the latter moreso than the former, but don't worry about the distinction right now). What's important to know about this block and its statements are in the accompaning comments:

FETCH_W                  $0, 'a'          /* Retreive the $a variable for writing */
ASSIGN $1, $0, 123 /* Assign the numeric value 123 to retreived variable 0 */
FETCH_W $2, 'b' /* Retreive the $b variable for writing */
ASSIGN $3, $2, 456 /* Assign the numeric value 456 to retreived variable 2 */
FETCH_R $5, 'a' /* Retreive the $a variable for reading */
FETCH_R $6, 'b' /* Retreive the $b variable for reading */
ADD ~7, $5, $6 /* Add the retreived variables (5 & 6) to gether and store the result in 7 */
FETCH_W $4, 'c' /* Retreive the $c variable for writing */
ASSIGN $8, $4, ~7 /* Assign the value in temporary variable 7 into retreived variable 4 */
FETCH_R $9, 'c' /* Retreive the $c variable for reading */
ECHO $9 /* Echo the retreived variable 9 */

Seem like a lot of work for one plus one? It is, here's the same code snippet compiled by ZE 2.1/PHP 5.1 (or later).

ASSIGN                   $0, !0, 123      /* Assign the numeric value 123 to compiled variable 0 */
ASSIGN $1, !1, 456 /* Assign the numeric value 456 to compiled variable 1 */
ADD ~2, !0, !1 /* Add compiled variable 0 to compiled variable 1 */
ASSIGN $3, !2, ~2 /* Assign the value of temporary variable 2 to compiled variable 2 */
ECHO !2 /* Echo the value of compiled variable 2 */

These !0 variables refer to a new structure in the execution stack which stores and references to the "real" variables out in userspace. The hash value for each variable is computed at compile time (meaning that it's only done once per variable no matter how often it's used and that opcode caches save this work from being done during subsequent page views at all). The first time one of these CVs is used, the engine looks it up in the active symbol table and updates the CV cache to know where it is. All subsequent uses of that compiled variable use that pre-fetched address and don't have to look it up again. On an individual lookup, this isn't a major leap forward in speed, however consider a for loop where the test value is checked on every iteration; To put it in PHP terms, which would you rather do?

for($i = 0; $i < foo =" lookup_variable('foo');">increment();
$foo = lookup_variable('foo');
$foo->check_value();
}

or

$foo = lookup_variable('foo');
for($i = 0; $i <>increment();
$foo->check_value();
}


What compiled variables are not

Don't assume you're going to get a speedup on all of your code, especially if you use arrays or objects (which most code taking advantage of PHP5's new features does). The CV speedup has one minor achilles heel: It only works on simple variables. Putting it in terms of opcodes, let's consider this PHP source:

<?php
$f->a = 123;
$f->b = 456;
$f->c = $f->a + $f->b;
echo $f->c;
?>

Basicly the same code right? Just a little oopified... Let's look at the ZE2.1/PHP5.1 compilation of that:

ASSIGN_OBJ                $0, !0, 'a'     /* Assign the numeric value 123 to property 'a' of compiled variable 0 object */
OP_DATA 123 /* Additional data for ASSIGN_OBJ opcode */
ASSIGN_OBJ $1, !0, 'b' /* Assign the numeric value 456 to property 'b' of compiled variable 0 object */
OP_DATA 456 /* Additional data for ASSIGN_OBJ opcode */
FETCH_OBJ_R $3, !0, 'a' /* Retreive property 'a' from compiled variable 0 object */
FETCH_OBJ_R $4, !0, 'b' /* Retreive property 'b' from compiled variable 0 object */
ADD ~5, $3, $4 /* Add those values and store the result in temp var 5 */
ASSIGN_OBJ $2, !0, 'c' /* Assign the ADD result to property 'c' of compiled variable 0 object */
OP_DATA ~5 /* Additional data for ASSIGN_OBJ opcode */
FETCH_OBJ_R $6, !0, 'c' /* Retreive property 'c' from compiled variable 0 object */
ECHO $6 /* Echo the value */

What's important to see here is that the properties are refetched each time a read or write is performed on them, which at first glance looks as bad as the pre PHP5.1 way of dealing with variables. Don't let your enthusiam for compiled variables blind you though. Remember the magic __get(), __set(), __offsetget(), and __offsetset() methods which objects allow for. These overloading tricks are great, but they mean that the variable returned by one fetch may not be the variable returned by a subsequent fetch. It's unfortunate that this can't be guaranteed, but it's the reality of a dynamic language like PHP. Know your particular class isn't overloaded? You can get that speedup (at least some of it) back by using good 'ol references to turn your object variables into simple variables:

<?php
$a = &$f->a;
$b = &$f->b;
$c = &$f->c;
$a = 123;
$b = 456;
$c = $a + $b;
echo $c;
?>

Becomes:

FETCH_OBJ_W               $0, !1, 'a'     /* Retreive property 'a' from compiled variable 1 object */
ASSIGN_REF $1, !0, $0 /* Make compiled variable 0 a reference to the retreived variable */
FETCH_OBJ_W $2, !1, 'b' /* Retreive property 'b' from compiled variable 1 object */
ASSIGN_REF $3, !2, $2 /* Make compiled variable 2 a reference to the retreived variable */
FETCH_OBJ_W $4, !1, 'c' /* Retreive property 'c' from compiled variable 1 object */
ASSIGN_REF $5, !3, $4 /* Make compiled variable 3 a reference to the retreived variable */
ASSIGN $6, !0, 123 /* Assign the numeric value 123 to compiled variable 0 */
ASSIGN $7, !2, 456 /* Assign the numeric value 456 to compiled variable 2 */
ADD ~8, !0, !2 /* Add compiled variable 0 to compiled variable 2 */
ASSIGN $9, !3, ~8 /* Assign the value of temporary variable 8 to compiled variable 3 */
ECHO !3 /* Echo the value of compiled variable 3 */

Now, this particular example is actually a few more opcodes, and a little bit more work for the engine too, but the more your code following this point uses the local/simple-variable/reference copies rather than the object copies, the balance will quickly tip towards your favor because the variables are already fetched, and they don't need to go through the expensive re-fetch process (which is worse for objects than it ever was for regular variables).


Another caveat to CVs, is that they are entirely scope-local. This should make since as $a in the globals scope is not the same as $a in a given function. What this means for your script, is that when execution enters a new function (execution scope) the CVs for that function are a blank slate and everything has to be fetched anew, even if that function was called before.

Globals and Statics

Statics and Globals are treated to CV status, but only by way of the reference trick I just mentioned:


<?php
static $bar;
echo $bar;

Turns into:

FETCH_W      static      $0, 'bar'
ASSIGN_REF !0, $0
ECHO !0

What does that mean for your use of the $GLOBALS array? That's right, the global keyword is technically faster. Now, I want to be really clear about one thing here. The minor speed affordance given by using your globals as localized CVs needs to be seriously weighed against the maintainability of looking at your code in five years and knowing that $foo came from the global scope. something_using($GLOBALS['foo']); will ALWAYS be clearer to you down the line than global $foo; /* buncha code */ something_using($foo); Don't be penny-wise and pound foolish..


No, seriously. This post shouldn't be taken as a guide-book to speeding up your apps, they're slow for other reasons.