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.

May 10, 2006

Doctor! Doctor! It hurts when I do this!

I came across a bug while working on runkit today. On testing a batch of changes I'd made I was suddenly* running into a buffer overrun error on any request which instantiates the Runkit_Sandbox class. A few valgrind and gdb rounds later and I'd traced the corruption to apc.enabled's ini_entry->value which was actually encountering a form of double-free.


Now, I have no illusions regarding the fact that runkit is a black sheep. The things it does are contrary to PHP's design and probably shouldn't be done, so my first assumption is that APC is not the one at fault and that runkit is just coincidentally stepping on APC's toes. Time to break out the caffeine and get dirty...

After a little more digging I notice this block of code in APC:


PHP_MINIT_FUNCTION(apc)
{
ZEND_INIT_MODULE_GLOBALS(apc, php_apc_init_globals, php_apc_shutdown_globals);

REGISTER_INI_ENTRIES();

/* Disable APC in cli mode unless overridden by apc.enable_cli */
if(!APCG(enable_cli) && !strcmp(sapi_module.name, "cli")) {
zend_alter_ini_entry("apc.enabled", sizeof("apc.enabled"), "0", 1,
PHP_INI_SYSTEM, PHP_INI_STAGE_ACTIVATE);
}

if (APCG(enabled)) {
apc_module_init(module_number TSRMLS_CC);
}

return SUCCESS;
}

Ignore for the moment the fact that it's claiming we're in the ACTIVATE stage (we're really in the STARTUP phase -- but that's not where the problem lies). The problem with this statement is that zend_alter_ini_entry() uses estrndup(), a call which assumes that thread storage has been spun up and that the per-request pointer list is ready for the new entry to be indexed. During MINIT, that's just not the case.


The "right" solution for this is to move the CLI mode override to RINIT and sure enough, this fixes the overrun/double-free issues just fine. The trouble is, it's also a wasteful proposition. Compound this by the fact that the error only presents itself when all of the following conditions are met:

  • APC being used on command line
  • apc.enable_cli switch not set
  • Thread Safety turned on (requires --enable-maintainer-zts during bulid)

Should APC take a performance hit in order to satisfy such an edge case scenario? &#@% NO! This is one bug that can be casually swept under the rug. Move along now, nothing to see here.


P.S. - In case you were wondering, the only reason I didn't notice this before is that I'd only just recently added APC to my development build. The coincidence of this bug showing up with this last batch of changes was precisely that, a coincidence.

May 8, 2006

re2c is no lemon

For the longest time I've been wanting to learn how to actually write a compiler. Sure, I've been twiddling the Zend Engine here and there for a few years, but there's this gulf between tweaking and actually building up from scratch which, until today, I had just never found my way across. Enter PDO_User, my most recent extension for implementing PDO drivers in userspace. Although some uses of PDO_User might be as simple as wrapping real database drivers that don't have a PDO version (e.g. informix), there's also a class of userspace implementation that might want to perform decidedly non-SQL actions via a SQL front end.


Since writing a compiler in userspace is... difficult to say the least, I decided this was the perfect opportunity to sharpen my teeth on the task and provide a generic SQL compiler via the PDO_User class. For the lexer I chose re2c as Marcus has been pushing that and what I'd seen of the syntax made it look pretty straight forward, for the parser I went with lemon which Wez is generally ga-ga over. I considerd flex and yacc, but tossed them out since I've already got a basic understanding of their syntaxes. I wanted a start fresh.

After a couple-three days and some aborted attempts, I've managed to implement a relatively feature-rich SQL tokenizer and compiler accessible to userspace via array PDO_User::tokenizeSQL($sql[, $ignore_whitespce=true]) and array PDO_User::parseSQL($sql). Those of you intimately familiar with SQL specs will certainly find faults in my grammar, and just about everyone should notice that there's no support for floating point numbers yet (a minor oversight). What's important is: (A) I've got a working implementation to build from, and (B) It was damned easy to learn both these tools.


Some thoughts on re2c and lemon:

  • Not enough samples provided on the manual pages. As an OSS documentation maintainer myself I know that this is easier to complain of than actually do something about.
  • I got stuck early on with re2c due to having an old version (the one packaged with debian) which didn't support case-insensitive matching. If you run into this problem, check your re2c version. If `re2c -v` isn't even able to output a version, then it's certainly too old.
  • What the @!#& is the "error" non-terminal for in lemon? The documentation talks about using it to gracefully deal with parse errors, but doesn't talk about how to use it.

If all you want is some sample output, have a look at this:

<?php
print_r(PDO_User::parseSQL("SELECT foo,bar as b,baz
FROM boop LEFT JOIN doop as d
ON (bid=did)"));



Array
(
[type] => statement
[statement] => select
[fields] => Array
(
[0] => foo
[1] => Array
(
[type] => alias
[field] => bar
[as] => b
)
[2] => baz
)
[from] => Array
(
[type] => join
[table1] => boop
[join-type] => left
[table2] => Array
(
[type] => alias
[table] => doop
[as] => d
)
[on] => Array
(
[type] => condition
[op1] => bid
[condition] => =
[op2] => did
)
)

[modifiers] =>
[terminating-semicolon] =>
)

May 3, 2006

PDO_User and the effects of Slide Driven Development

Last Fall, Wez and I (and others) met up at the Zend conferrence in Burlingame, CA (US). This was primarily a pre-PDM checkin for those of us who wouldn't be able to make it to Paris to get our thoughts in on the direction of PHP6. Apart from these topics, Wez mentioned wanting to have a userspace bridge for implementing PDO drivers, but that there was entirely too much going on that was (in all honesty) more important.


I agreed on both counts. At the time I was doing my best to ignore the temptation to work on operator... Click through and you'll see just how weak willed I can be :)

Fast forward to last week in Orlando, FL (US) and the php|tek conferrence. As mentioned in earlier blog posts, I think my presentations went over okay for a first couple of talks, but if I have to make one complaint, it's that the subject matter was ill-aimed. I spoke to Sean (one of the conferrence organizers) after the closing keynote and asked him about the upcoming php|works conf in toronto. When he said that one of the focuses would be on database interaction (since it would be taking place along side db|works), my thoughts immediately jumped to the PDO userspace driver which had been running around the back of my mind for months.


By the end of my flight back I had half the implementation written and had generally come to the conclusion that the PDO API was pretty damned nice. Long story short, I've just uploaded release 0.1 of pdo_user which is ready for testing. The file you'll want to pay attention to is: README.OBJECTS as that describes the mechanics of actually implementing a driver in userspace. Another interresting spot to look is tests/globals.phpt which includes a simple, yet functional userspace driver for accessing variable contents as database resources.


I don't expect it to work perfect right out of the box, but it should get the job done. Please Report Bugs when you find them so I can get 'em fixed.