Dec 28, 2006

PHP-2006: A look back

Well, Davey Shafik started us off with his year-end wrapup so I'll follow suit with mine. The thoughts below are mine and mildly influenced by alcohol. They represent a foggy review of how I experienced the year through the imperfect recollection of mailing list archives.

January began with releases of 4.4.2 and 5.1.2. Version 5.1.2 was especially close to my heart since it was the first version to ship an extension of mine not only bundled, but enabled by default. I've had my hands in most of the PHP runtime, but this was the first time I could point at a standard extension and say that it was basicly my work (Note: Mike Wallner did a fair bit of work adding to the number of hash algorithms supported, don't let me discount his efforts). Tim Starling wrote to ask why PHP4 refcounts are 16bit, and what he could do about getting that counter increased in future versions of PHP4. Since such a change would break binary compatability and since the PHP4 branch is already quite dead, the request was ultimately left alone with an admonishment to "not do that".

A few other requests were broached or continued, such as support for Friend Classes, Named Arguments, and Naming Arguments (The last of which would eventually be implemented). It was also this month in which Rasmus suggested adding JSON to the standard distribution, this quietly morphed into votes for including filter; Both were eventually linked in. James Crane had the idea that Array Literals in PHP could use sprucing up, meanwhile Sean Coates planted the seed for what would become the PHP6 Unicode Progress tracker.

February came in fairly quiet, mostly wrapping up the topics from January. Appearantly Steph didn't like the quiet and decided to stir up the hornet's nest with some four letter words cleverly disguised under the heading of True Labeled Breaks. For those who have blocked out 2005, this was easily one of the longest threads of that year and this resumption promised to be just as bad. By March, this thread would finnally be brought to a halt as the functionality was slipped quietly into the engine. I did not see that coming...

Amidst this unexpected addition, others were busily pulling things out, with Andi slashing away at safe_mode in HEAD, and Marcus integrating support for function deprecation into the 5.2 branch.

March came in like a lion with Marcus poking the list about Late Static Binding with less argument about Why, and a healthy focus on How. Johannes Schlueter made a very pragmatic and uncontroversial suggestion to change the internal symbol prefix applied to methods in order to distinguish them from functions. Pierre ran with some PDM recommendations shouting Adieu register_globals and Adieu a la magie. Sebastian Bergmann was frustrated with trying to apply streams filters to include and require, until I pointed him at the php://filter wrapper. Though this met his need, he made a strong case for being able to set an automatic filter to be applied to all streams. I promised to do this after streams in HEAD had been cleaned up, and while I've gotten streams where I want them, I still havn't fullfilled his feature request... Shame on me.

All this time, the GOTO debate was still raging and similar types of language altering features were being put on the table. Finally, Zeev decided he'd had enough calling for people to "Give the language a rest". This managed to have a decent effect which Rasmus soon channeled into his call for performance geeks to narrow the performance gap between 4.4 and 5.1. Towards the end of the month, I suggested adding an open_basedir_for_include directive. While there was some interrest for this, Ilia quickly convinced me that my approach to the problem was flawed and wouldn't give any real benefit.

April showered the internals list with Round 2 of the Late Static Binding discussion, and Thomas Boutell's anouncement that he'd be turning over primary development of the GD library to the PHP project, spearheaded by Pierre. I made some more noise complaining of RETURN_RT_STRING() (and family)'s leakage of memory under certain not-uncommon conditions, while Nuno brought the infamous Coverity Report to attention.

Richard Lynch shared his WTF concerning the oft misunderstood tsrm_ls parameter in PHP's sources. Sadly, I hadn't written my summary of the topic yet, but although noone gave him a detailed description (It's a bit long to go into in an email), several helpful links were supplied. Round about the middle of the month, Rasmus announced PHP's eminent participation in the Google Summer of Code project (wait, don't you work for Yahoo!?). After signing up as a mentor (though I never actually mented -- is that a word?), I gathered up my materials and flew off to php|tek 2006 in Orlando.

May opened to the initial planning rounds for PHP 5.2.0, and the resumption of the ifsetor() request, now dressed up as coalesce(). By the way, why havn't we embraced this feature request yet? About a week into the month, Ilia branched the PHP_5 tree leaving room for the earnest development of 5.2.

William Candillon wrote in to request a PHP version of C's #line macro, but was drowned out by the roar of Derick's plea to "Stop breaking our apps for the sake of OO". Once the din of that had quieted down a little, Jason Garber decided to ask about making it possible to mark properties as read only. In case anyone was curious, none of these proposals gained footing.

June now, and my book is finally released bringing with it an invitation to appear on php|architect's Pro::PHP podcast. Marcus Boerger suggested that array indices could benefit from implicit __toString() calls, but he was eventually shot down. Dimitry's suggestion however, which provided for automatic module global registration via the module entry, did receive a warm welcome and you'll see it if you look inside PHP 5.2.0.

Clearly this was the month of bright ideas, because Nuno Lopes tossed in gcc branch prediction. It got a warm initial reception from the engine folk including Zeev stating "I actually like how it makes the code more readable hinting which branches are rare.". Andi agreed that it was a nice idea, but brought in the sobering reality that it didn't really do much for performance and could potentially bring unexpected results if applied heavily.

July was a busy month for me as I changed jobs for the first time in over six years. I'm loving the new job by the way. Fantastic coworkers and....interresting challenges.... Laupretre François thought it'd be a good idea to extend include_path to support stream wrappers. Those of us who know what trouble include_path already causes for performance and security were quick to nix that particular idea; Short version: No.

Marcus popped in mid-month with an implementation of the #line directive suggested back in May. Still no love from the internals community at large though; Short version: no #line for you. Dmitry committed a large patch to the Zend Engine to change how non-persistent memory is allocated and freed within a request. The good news is that emallocs are now faster, the bad news is that a hack I'd made in PHP5.1 for manipulating the non-persistent memory pool no longer worked...Grrrr.....

On July 27th, Jani said Good-Bye.

August picked up Mike Wallner's July post decrying the state of OO strictness building up in PHP. This maelstrom took up most of the first week with no real conclusion (at least, not a satisfactory one). A different Mike made some more noise later in the month asking why accessing non-existant functions/methods has to be so darned fatal. The good news is that they aren't so much anymore. Yay for E_RECOVERABLE_ERROR.

September brought a landslide of movement in the PHP6 function migration/review process. There was some degree of question as to whether unicode.semantics should be SYSTEM, PERDIR, or USER. Noone really considered the latter as a possibility as it wrecks way too many assumptions, but the SYSTEM/PERDIR debate raged for awhile with the idealists wanting PERDIR support to aid migration, and the realists clinging to the maintainable simplicity of SYSTEM. In the end, SYSTEM won.

October was a relatively slow month, leading up to conference season such as it was. Midmonth sometime I tossed out the idea of allowing open_basedir to be tightened (but not loosened) during runtime and it was green-lighted surprisingly quietly. Ilia got a little frustrated with the mounting delays holding back the release of PHP 5.2.0 which was already way behind schedule. Tragicly, this plea made the delay last even longer. Finally at the end of the month, he rolled final.

November was a big conference month for me. First I crashed the nearby ZendCon, then turned right around and flew off to Germany for the International PHP Conference. Sean poked the namespaces topic with a sharp stick and managed to generate a week's worth of noise resulting in no actual commitments by any capable/interrested parties. No sooner had that comotion died down than did Antony Dovgal bemoan a regression in fgets()'s behavior which I introduced in HEAD. I still think it's a silly behavior for a userspace function, but BCs are BCs, so the regression's been reverted.

December's most exciting event was a debaucle over the backward/forward compatability of serialize given the additional escaping concerns that processing unicode strings imposes. Ilia brought forward a proposal to finally remove the unnecessary COM/Sockets/MHash extensions from the distribution bundle. After some light debate it was decided to keep COM around and only nix Sockets and MHash as of PHP6 (possibly 5.3 if such a version comes about). Wietse Venema brought back the concept of introducing a taint mode for PHP. The topic is still being discussed, but things don't look good for Wietse...

And now here we are, at year's end. PHP6's unicode function migration process has passed the 50% mark and a preview release is sure to come once we handle a couple more extensions. PHP 5.2 is grabbing hold amongst the serious PHP shops, and even web hosters are trickling away from PHP4. There's still a huge, bright future ahead for this language, together we can make it happen.

¡Feliz Año Nuevo!

Nov 29, 2006

When good encodings go bad

In the past year, I've been doing some work with Unicode as part of the PHP6 upgrade. I've learned more than I wanted to know about all sorts of encodings from UTF-7 to koi8-r to good old iso-8859-1. I've picked apart the picayune differences between UCS-2 and UTF-16, and played the game of surrogate pairing and orphaning. Despite all that exposure however, I wasn't prepared when a question crossed my inbox about a lesser known encoding called AL32UTF8.

I'd never heard of this one before, so I went to my favorite search engine for some answers. Turns out it's something Oracle came up with and later got adopted as a proper standard with the name CESU-8. At first glance, CESU-8 looks identical to UTF-8 in the same way that UCS-2 looks a lot like UTF-16. In fact every codepoint from U+0000 to U+FFFF is encoded identically under both sets of rules: 16 bits, split up over one, two, or three bytes, with leftover bits framing the encoding protocol.

When you jump up above U+FFFF however, into the realm of CJK codepoints and the like (such as my personal nom du pointe: 𣚺) something funny starts to happen. In the UTF8 world, these codepoints are accomodated by adding one extra byte to the mix which allows for up to 22bits of data (All of unicode only requires 21). In the CESU-8 world however, the code point is split according to UTF-16 surrogacy rules making two separate unicode points (each in the range U+D800 - U+DFFF). These two unicode points are then encoded individually into UTF-8 sequences. This means that we've now promoted our variable length (4 max) multibyte encoding to a variable length (6 max) multibyte-multibyte encoding. Thank you Oracle. Thank you for adding complexity to encoding rules while increasing data storage requirements. What would the world do without you?

P.S. - Java is at fault too... its 'Modified UTF-8' uses nearly identical rules.

Nov 5, 2006

Don't worry, I slept last Thursday

On this, my first foray to Europe, indeed my first real trip outside the US (Those couple hours in Tijuana don't count), I'm faced with one undeniable, inexcapable fact. Jet lag sucks.

It doesn't help that all last week I was staying up past my normal bedtime partying with the attendees of ZendCon06, but I think my real mistake was trying to outsmart my own circadian rhythm. See, I figured "I've got this long flight across the atlantic, it'll go faster if I can fall asleep at some point." Seems reasonable so far. How to ensure sleep? Why, stay up all night before the flight. Brilliant! But wait, what if I can't fall asleep during the flight?

Sometime sunday morning, my plane lands in Frankfurt and I wander, zombie-like, through passport control, baggage claim, and customs somehow managing to board the right shuttle to reach the conference hotel. Based on advices from battle-hardened globetrotters, I was planning to put in a one hour power nap, then go for a walk to "reset" my internal clock. Unfortunately the sixty-plus hour run of consciousness had other plans and by the time I awoke, the sun had set.

Finally this morning (Monday), I managed to put in that walk, touring a nearby suburb which reminded me somewhat spookily of the setting from "Shaun of the Dead" (more zombie tie-ins). Five euros and three liter bottles of diet coke later and I'm almost coherent enough to....what the hell was I gonna say? Sorry, my brain has been cutting out a lot the past few days...

Guten Morgen!

Jul 5, 2006

Moving right along

After well over half a decade at the University of California at Berkeley, I'm moving on to greener pastures. Well, maybe not greener (Berkeley is full of evergreens and...other verdant plant substances), but pastures at the very least. Next monday, my life's journy will bring me to that farmland come technopolis known the world over as Silicon Valley. I'll be cubefarming private sector style for one of the few dotcom survivors, a little mom & pop outfit called Yahoo!. I've worked in education and the public sector since 1997 and while I'm optimistic everything will go smoothly, well....I think Dan Aykroyd put it best as Dr. Raymond Stantz in Ghostbusters: "You don't know what it's like out there, you've never worked in the private sector, They expect results! (shudder)". If nothing else it'll be nice to get my commute back down to the 20minute zone. At any rate, I've got 2 days and as many going-away parties to get through, then it's on to the enemy camp (Yahoo's birth {not as a company, but as the index site it started out as} was at UCBerkeley's long-time rival, Stanford University).

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 7, 2006

Extending and Embedding PHP


It's official!!!! After a year in development Extending and Embedding PHP is now shipping from fine book stores everywhere.


I've gotten good reviews from the half dozen people I know who've gotten their hands on it, and I am really satisfied with most of it. Do I think it could be better? Of course I do, but I don't think I was ever going to be completely satisfied.


I've learned a lot through this process and while I don't see any more titles in my immediate future (there are things coming down the pipe which are likely to change my availability), I do expect that my next book, should it materialize, will be even better.


If you pre-ordered it, you should see it soon, if you've already got your copy, let me know what you think! Did I skim over some topic too quickly? Did I belabour something else? If this book eventually finds it's way into a 2nd edition (no promises mind you), are there topics you want to see added? Tossed out? Expanded/Compressed?

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.

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.

Apr 28, 2006

Let's go out to the movies

As promised yesterday, I've gotten the videos from php|tek uploaded. Note: They're all coverage of the beer chugging contest, so don't expect to be clicking into any presentations.

Apr 23, 2006

Please don't code like this...

As a component of my job, I'm overseeing a fledgling developer in another department as he sharpens his teeth on putting together a simple application for one of my units. The first draft of his code was....well....It's a good example of why some people associate PHP with insecure applications. After some lengthy back and forths we've cleaned up the security related problems and gotten the functionality working. In my recent code review however, I came across this gem:

SELECT userid FROM (
SELECT COMPETENCY.userid,
SUM(CASE WHEN COMPETENCY.cid = 1 THEN 1 ELSE 0 END) AS comp_01,
SUM(CASE WHEN COMPETENCY.cid = 2 THEN 1 ELSE 0 END) AS comp_02,
SUM(CASE WHEN COMPETENCY.cid = 3 THEN 1 ELSE 0 END) AS comp_03,
SUM(CASE WHEN COMPETENCY.cid = 4 THEN 1 ELSE 0 END) AS comp_04,
SUM(CASE WHEN COMPETENCY.cid = 5 THEN 1 ELSE 0 END) AS comp_05,
...You get the idea...
SUM(CASE WHEN COMPETENCY.cid = 35 THEN 1 ELSE 0 END) AS comp_35,
SUM(CASE WHEN COMPETENCY.cid = 36 THEN 1 ELSE 0 END) AS comp_36,
SUM(CASE WHEN COMPETENCY.cid = 37 THEN 1 ELSE 0 END) AS comp_37,
SUM(CASE WHEN COMPETENCY.cid = 38 THEN 1 ELSE 0 END) AS comp_38,
SUM(CASE WHEN COMPETENCY.cid = 39 THEN 1 ELSE 0 END) AS comp_39,
SUM(CASE WHEN COMPETENCY.cid = 40 THEN 1 ELSE 0 END) AS comp_40,
FROM COMPETENCY, COMPETENCY_CODE
WHERE COMPETENCY.cid = COMPETENCY_CODE.cid
GROUP BY COMPETENCY.userid) AS my_view
WHERE (comp_20 = 1 AND comp_19 = 1)

Now, I generally don't like to be TOO abrasive with new developers, don't want to scare them off or anything, but can someone buy this child a SQL book?

Apr 18, 2006

Simple Pleasures

About a week ago a coworker came to me for recommendations on hooking up some broadband at home. I gave her the usual breakdown of the differences between Cable and DSL, what she would need in her laptop to make it connect properly, blah blah blah... So after a little back and forth she settled on a DSL package which offered a decent price and a wireless package which she was told initially included a pcmcia card (later found out it didn't, but they'd be happy to sell her one...).

Since her 266Mhz laptop (running Win98E) only had 28MB of memory we ordered her up a 128MB stick to fill the empty slot (she's trying to do this all on a constrained budget, but there are limits!). We got the memory installed and plugged in an old 10BT pcmcia card I had lying around (a useless spare part to me, but a helpful token for her while she waited for the wireless card to appear) and she was up and running.

A few days later the 802.11g card appeared and this is where things went wonky. She installed the full set of the ISP's software (which included absolutely pointless crap that I'd avoided for her initially when I put the wired card in) in order to get to the wireless card drivers. Not only did the wireless card not work, but the setup died midway, the wired connection stopped working, several random error dialogs started appearing, and the coup de gras.... The backlight on the LCD winked out completely. In short, her one-year contract with the ISP was going to be worthless because her only computer had just exploded.

So she brought the PC back to work with a heartwrenching face of woe. After two days of twiddling with it in between the stuff UC actually pays me to be doing, the laptop is officially singing. The software problems just required uninstalling the ISP's mierda, getting updated drivers from the card vendor, and a tiny bit of registry hacking, nothing phenomenal for a IT person, but not something she could have managed without a reinstall (the process of which would have likely led to the same end result). The LCD was a bit trickier, turns out the lead from the step-up transformer to the backlight lamp had snapped off (coincidental to the software problem, though perhaps aggravated by her frustration induced thwacking) at the bulb so I had to tear open the whole panel and resolder it. Again, nothing more amazing than (effectively) reseating a cable, but this was fun because I'd never dissasembled a panel to that degree before.

Best part of all, when I opened up her laptop to show a vibrant screen (actually better than it had looked before all this started) and successful connectivity to AirBears (UCB's wireless network), the look on her face just said it all.

Sometimes, doing helpdesk work kicks ass.

Good Old Times

Andrei and Zak recently posted their "Good Old Times" references to getting into the PHP project so I thought I'd put mine forward for good measure. Never let it be said I'm not a follower...


At any rate, it didn't take long for Derick to suggest I refer to the CODING_STANDARDS and resubmit the patch as a unified diff against HEAD. After resubmitting, the patch was accepted and, bouyed by my response, led to another contribution in the form of the getanyrr() function which would later come to be named dns_get_record().


Three and a half years later and I've managed to touch nearly every part of the PHP Core distribution and make a lot of friends (and perhaps some not-so-friendlies) along the way. Cheers to all, you know who you are.

Apr 10, 2006

Bunnies bunnies everywhere

I've long been convinced that coincidence is never an accident. On a day to day basis, the questions that come through ##php will gather in bunches to the point where I can often just up-key to find the last time I answered the question, and state it again. What does this have to do with bunnies? I'm glad you asked.

It started with a random link posted in IRC: http://blog.jonnay.net/archives/632-Introducing-BunnyRegex-easy-regular-expressions,-and-mini-languages-inside-of-PHP..html With a title like that, who can resist? The topic itself is fairly uninterresting, however it turned the conversation to rabbits, or at least rabbit stew.


Within moments, a coworker came by my office with a thank-you present (I'd helped her upgrade her personal laptop and get setup with DSL at home). As part of this thank-you present she offered up a stuffed animal (okay, so I have a thing for stuffed animals, sue me). Guess what species she'd selected? That's right. Wabbit.

Is it Fox Mulder spooky? No. But it's a little bit freak-o...


Speaking of rabbits: Check out my cat (who happens to be named "Bunny"):

Jan 4, 2006

You're not bad...for a girl...

In response to http://news.php.net/php.internals/21374 I offered up a fairly simple extension dubbed "callable" (note, I never wound up dropping this in PECL) which overrides the builtin is_callable() function to call $object->__callable($methodname) in order to determine if a __call() overloaded object is actually capable of calling a particular method.


Those who wanted the funcitonality were happy to have the code and it brought the already-too-long discussion to a close saving list traffic for more important topics.


That same day, one of the list lurkers sent me an email which included the following.....quizzical paragraph:


Not to sound chauvinistic or anything and not meant to be, but you are a woman, I'm very impressed with the code, it's direct and too the point, not what I would expect from someone of your gender as most female programmers I've come across are into a lot of fluff and eye-candy and not so much in pure functionality.

Now, I'm about 95% certain that he did, in fact mean it as a compliment. And while I appreciate the....hrmmm praise isn't quite the right word...good wishes, I'm not really really clear on just how appreciative I should be. It's a little like what I would expect hearing "You're remarkably well spoken for a black person*." would be like... Or something.... How do you successfully answer a comment like that? Is it even possible?


*Not the cliche'd expression, but my liberal-guilt won't let me use it, even as an example of poor taste.