Donnerstag, 12. Juni 2014

Tears in the Rain

<?php
class X {
    public function foo() {
        echo "foo";
    }
}
function foo() {
    echo "global foo";
}
X:foo();

This will happily print "global foo" on your screen. No complaints by the parser (nor by NetBeans or PHPStorm...)

In case you're wondering why this is strange, take a look at the T_ACHAT_NEKUDA between X and foo().

Yay.

Mittwoch, 19. März 2014

Performance Penalties In JavaScript: Recursion vs. Iteration / Native DOM Objects vs. jQuery

In one of my own private projects (codename "Tag Bonsai"), a little chrome extension for working with tags, I ran into some performance issues. I figured this was down to recursive creation of DOM tags, so I ran a little test. (Find the full code at the bottom of this posting!)

The results were astounding.

They were not astounding, though, regarding their general direction. Of course iteration is faster than recursion, and of course native JS objects are faster than their jQuery sires.

What was astounding was the sheer factor of performance penalties involved. On my puter, those are the results when creating 10000 spans:

recursive on jq objects: 11567.999999999302
recursive on js objects: 348.99999998742715
recursive on acc'ed html: 82.00000002398156
iterative on jq objects (without penalty): 10442.000000010012
iterative on js objects (without penalty)201.9999999902211
iterative on acc'ed html (without penalty): 41.99999998672865
iterative on jq objects (with penalty): 9248.00000002142
iterative on js objects (with penalty): 425.99999997764826
iterative on acc'ed html (with penalty): 61.000000016065314
 That's a factor of ~ 190 when writing html manually in an iterative loop, compared to recursively creating jQery objects, even if I penalize the iterative by doing a push and pull each time (because under production circumstances, I'd have to manage a stack of objects by hand).

Here are the results again, ordered by time:
iterative on acc'ed html (without penalty): 41.99999998672865
iterative on acc'ed html (with penalty): 61.000000016065314
recursive on acc'ed html: 82.00000002398156
iterative on js objects (without penalty): 201.9999999902211
recursive on js objects: 348.99999998742715
iterative on js objects (with penalty): 425.99999997764826
iterative on jq objects (with penalty): 9248.00000002142
iterative on jq objects (without penalty): 10442.000000010012
recursive on jq objects: 11567.999999999302

Okay, JS objects are obviously a bit slower than pure html text (by a factor of 3 or so), and recursion drops the speed by another 3rd or so; but jQuery is an awful lot slower than that!

According to this simple test, jQuery objects involve a forbidding impact on performance, especially when combined with recursion. (It's interesting that iteration w/ jQuery and recursion w/ JS objects are relatively close together.) So if you have to create lots of DOM objects, and if you know which browser you use, definitely use the native JavaScript methods!

(I'm not quite sure why some penalized versions seem to be faster than their non-penalized counterparts - there might be some bug in my code, or it's just some random imponderability in my machine; but I think the general direction is still correct and as revealing as a skimpy white dress under heavy rain. It's just staggering.)



Here's the code (The StopWatch class is taken from this Stackoverflow posting.):

            var limit = 10000;
         
            function createSpanRecJqObj(i, el) {              
                var div = $('<span>' + i + '</span>').appendTo(el);
                if (i < limit) {
                    createSpanRecJqObj(++i, div);
                }              
            }
            function createSpanRecHtml(i) {              
                var html = '<span>' + i + '</span>';
                if (i < limit) {
                    html += createSpanRecHtml(++i);
                }
                return html;
            }
            function createSpanRecJsObj(i, el) {
                var span = document.createElement('span');
                span.appendChild(document.createTextNode(i));
                el.appendChild(span);
                if (i < limit) {
                    createSpanRecJsObj(++i, span);
                }
            }
         
         
         
            function createSpanIterJqObj(i, el, penalty) {
                var stack = [];
                if (arguments.length === 2) penalty = false;
                var i;
                for (i = 0; i < limit; ++i) {
                    if (penalty) stack.push(i);
                    el = $('<span>' + i + '</span>').appendTo(el);
                    if (penalty) stack.pop();
                }              
            }          
            function createSpanIterHtml(i, penalty) {              
                if (arguments.length === 1) penalty = false;
                var html = '';
                var stack = [];
                for (var i = 0; i < limit; ++i) {
                    if (penalty) stack.push(i);
                    html += '<span>' + i;
                }
                for (var i = 0; i < limit; ++i) {
                    if (penalty) stack.pop();
                    html += '</span>';
                }
                return html;
            }
            function createSpanIterJsObj(i, el, penalty) {
                var stack = [];
                if (arguments.length === 2) penalty = false;
                console.log('penalty', penalty);
                var i;
                for (i = 0; i < limit; ++i) {
                    if (penalty) stack.push(i);
                    var span = document.createElement('span');
                    span.appendChild(document.createTextNode(i));
                    el.appendChild(span);
                    el = span;
                    if (penalty) stack.pop();
                }
            }
         
         
            $(document).ready(function(){
                var sw = new StopWatch();
                sw.start();
                createSpanRecJqObj(0, $('body'));
                var timeRecJqObj = sw.stop();
                $('#time').append('recursive on jq objects: ' + timeRecJqObj + '<br/>');
             
                sw.start();
                createSpanRecJsObj(0, document.getElementsByTagName('body')[0]);
                var timeRecJsObj = sw.stop();
                $('#time').append('recursive on js objects: ' + timeRecJsObj + '<br/>');
             
                sw.start();
                $('body').append($(createSpanRecHtml(0)));
                var timeRecHtml = sw.stop();
                $('#time').append('recursive on acc\'ed html: ' + timeRecHtml + '<br/>');
             
                sw.start();
                createSpanIterJqObj(0, $('body'));
                timeIterJqObj = sw.stop();
                $('#time').append('iterative on jq objects (without penalty): ' + timeIterJqObj + '<br/>');
             
                sw.start();
                createSpanIterJsObj(0, document.getElementsByTagName('body')[0]);
                timeIterJsObj = sw.stop();
                $('#time').append('iterative on js objects (without penalty)' + timeIterJsObj + '<br/>');
             
                sw.start();
                $('body').append($(createSpanIterHtml(0)));
                timeIterHtml = sw.stop();
                $('#time').append('iterative on acc\'ed html (without penalty): ' + timeIterHtml + '<br/>');
             
                sw.start();
                createSpanIterJqObj(0, $('body'), true);
                timeIterJqObj = sw.stop();
                $('#time').append('iterative on jq objects (with penalty): ' + timeIterJqObj + '<br/>');
             
                sw.start();
                createSpanIterJsObj(0, document.getElementsByTagName('body')[0], true);
                timeIterJsObj = sw.stop();
                $('#time').append('iterative on js objects (with penalty)' + timeIterJsObj + '<br/>');
             
                sw.start();
                $('body').append($(createSpanIterHtml(0, true)));
                timeIterHtml = sw.stop();
                $('#time').append('iterative on acc\'ed html (with penalty): ' + timeIterHtml + '<br/>');
             
            });

Mittwoch, 19. Februar 2014

Optional Parameters in Javascript

Want a nice laugh? Here it comes.

For years, I've been hacking in JavaScript, as it were, "on the side". Just as a necessary, sometimes fun, often loathesome part of the webproject experience.

When doing optional parameters, I always did this:

function foo(a) {
    if (arguments.length === 2)
        b = arguments[1];
    else
        b = 'bar';
}

The problem with this is so obvious I won't even spell it out.

Here's the puncher.

What I somehow managed to miss until today, is that you can also do this:

function foo(a, b) {
    if (typeof(b) === 'undefined')
        b = 'bar';
}

Shorter, sweeter, more obvious. If I'd just known this a bit earlier.

HAML, HTML, Ruby, and Marketing

I just took a look at HAML, having been linked there from Mark Hansen's blog posting @ http://www.markhansen.co.nz/autocompiling-haml/.

After about 2 minutes, I had two questions on my mind:

1.
Why, oh why, do ruby projects tend towards this massive, obtrusive self-marketing? "templating haiku", "Simplify. Enjoy. Laugh. 20 minutes later, you will never go back." To me, this just seems silly. Most of your users are technically-minded coders who will evaluate your project based on exactly one question: Will it make their lives easier?

When I look at, for example, www.vim.org, www.php.net, or, heck, even www.microsoft.com, I don't see that. Ain't nobody as can claim those are unpopular, failed sidenotes of IT history.

2.
Which leads me to my second question: In what way exactly is HAML more beautiful, elegant, or expressive than HTML? I mean, yeah, it has nifty "=" and "%" signs all over the place, and it doesn't duplicate code in closing tags, so that's an advantage I guess. On all the other hands, it seems to use semantic whitespace, which I loathe. I want my code surrondedwith fancy "{"s! I get to decide how to indent, and what, and why!!!!!111one!!

Maybe it integrates nicely into ruby/rails. I wouldn't know, I'm just a lowly PHP hacker. (It feeds me and my cats, so don't ask.) Maybe it's just a matter of taste. Maybe I'm gettin' too old for this sh*t. Or I'm not good enough.

Seriously, I just don't know.

Samstag, 5. Oktober 2013

Optional Parameters in PHP, and NULL

Consider the following PHP5 code:

<?php
class A{
      function foo() {}
}
function f(A $a = NULL) { var_dump($a); }
function g(A $a) { var_dump($a); }
f(new A());
f(NULL);
g(new A());
g(NULL);


Now, consider its output:

class A#1 (0) {
}
NULL
class A#1 (0) {
}

Catchable fatal error: Argument 1 passed to g() must be an instance of A, null given, called in a.php on line 13 and defined in a.php on line 8

Okay. So, a parameter, with or without a type hint, whether required or optional, receives an A if given an A. No surprise there.

An optional, type-hinted parameter, when given a NULL, results in a NULL. I guess that's fine, too.

However, a required parameter can not ever be set to NULL.

You're right: Setting something to NULL in the parameter list means something totally different than setting something to NULL anywhere else in your code.

I guess what they were trying to do was force the = NULL, passing NULL "idiom" to represent the fact that the parameter is optional, and is not set. I guess the reason is that you can not default a type-hinted parameter to anything besides NULL. The function body can not distinguish between a NULL stemming from the default in the parameter list, and an actual NULL, and NULL is the only indication that the optional parameter was left out, so passing a NULL is disallowed in the case of a required parameter.

Or something like that. (If you omit the type hint, passing the NULL works perfectly fine, by the way, thus making it even more confusing...)

Regardless of how long, or how hard I think about it, it keeps making no sense at all. It is neither consistent, nor is it useful. It is, at best, an attempt of the language to be clever - an attempt, I should say, that went terribly terribly wrong. At worst, it is a result of some obscurity in the parser that nobody ever really thought through.

In any case, it makes me even more sad to be forced every working day to work with tools as broken as that.

Dienstag, 24. September 2013

Magento and Getters

From the Magento core code (in class Magento_Core_Model_App):

    /**
     * Retrieve request object
     *
     * @return Mage_Core_Controller_Request_Http
     */
    public function getRequest()
    {
        if (empty($this->_request)) {
            $this->_request = new Mage_Core_Controller_Request_Http();
        }
        return $this->_request;
    }


Folks... puleaze!!!?

I just wasted about an hour on finding out where $this->_request was initialized. I debugged over $this->getRequest() about a gazillion times. This is just not the way to write code, for crying out loud! The phpdoc block actually states this, right up there: Retrieve request object. Retrieve!!!!!!!!1112. It doesn't say anything about initializing it.

I mean, yeah, by now I should probably be able to anticipate stuff like that, seeing as some of the magento standard models' load() methods have an optional parameter $create that defaults to... you guessed it! True!!

Some. Not all. That would be too consistent (even if it's decidedly wrong).

Duh.

Dienstag, 10. September 2013

realX

I know that this is not a very unique idea, but it's such a common mistake one cannot be reminded of it often enough.

Only recently, I stumbled upon a code fragment that performed something akin to sudo on a webshop. It took a customer_id from the request, checked the logged-in customer's permissions, and then it went about and set a

realCustomer

to the customer from the session... or it set the customer from the request to the realCustomer, while the session customer remained untouched.... or...

See what I'm aiming at?

If you're ever tempted to use something like "real" to distinguish between two objects, please take the time and think about what that object actually represents. The adminCustomer, maybe, the loggedInCustomer, or the effectiveCustomer. But never the realCustomer.

thesaurus.com is a valuable, and deeply underappreciated, resource for coders.