views:

258

answers:

6

In this question a code bit is presented and the questioner wants to make it faster by eliminating the use of variables. Seems to me he's looking in the wrong place, but far be it from me to know. Here's the code

while ($item = current($data))
{
    echo '<ATTR>',$item, '</ATTR>', "\n";
    next($data);
}

Seems to me that the recreation of the strings <ATTR> etc. -- more than once on each line and every time the line is processed -- would have a cost associated with them (both in terms of speed and memory). Or perhaps the PHP processor smart enough so that there's no penalty to not putting the strings into variables before the loop?

I use variables for clarity and centralization in any case, but: is there a cost associated with using variables, not using variables, or what? (Anybody who wants to answer for other similar languages please feel free.)

+1  A: 

Actually this is probably the fastest implementation. You could try to concat all in to one string but all of the concat operations are pretty expensive.

Brad Heller
Really? I've been operating under the assumption that concat is a relatively low cost operation in PHP, given its mutable strings and all.
Alan Storm
Thanks... the question was (apparently badly worded and explained) what happens if you throw stuff into variables BEFORE reaching the loop instead of defining all the strings (e.g., <ATTR>) each time around.
Yar
+1  A: 

Everything has a cost. The goal is to minimize that cost as much as possible.

If you were thinking about concatenation check this resource for information on its performance. It's probably best to leave the code as-is.

Fake Code Monkey Rashid
Thanks for that. I don't think the code, as is, is particularly readable. I believe that PHP devs' avoidance of variables (and methods, and classes) is not only suboptimal in terms of speed and memory usage, butit's also harder to read.
Yar
+3  A: 

Hey guys here's an interesting one, my initial tests show that storing the newline char into a variable instead of PHP parsing it with each iteration is faster... See below

$nl = "\n";
while ($item = current($data))
{
    echo '<ATTR>',$item, '</ATTR>',$nl;
    next($data);
}
Kane Wallmann
This makes sense. You will get different results using a PHP optimizer I am certain (where the original code and your variable variant will perform the same once cached).
cfeduke
You would probably get better speed if you put the newline in with the closing tag - "</ATTR>\n"
too much php
And my original guess was that even putting "<ATTR>" into a variable and not defining it each time is not only easier to read, but ALSO easier on the PHP processor (i.e.., faster). Thanks!
Yar
+2  A: 
hobodave
Thanks for that. I think you're probably right (and I personally believe optimization is generally overdone anyway). For your tests it would be nice to try them with something in the strings, though it will probably make no difference. Didn't know about the PHP_EOL...
Yar
+3  A: 

If you really want to micro-optimize this way (I don't think it is that relevant or useful, btw -- but I understand it's fun ^^ ), you can have a look at a PHP extension called Vulcan Logic Disassembler

It allows you to get the bytecode generated for a PHP script.

Then, you must use a command like this one, in command line, to launch the script :

php -dextension=vld.so -dvld.active=1 tests/temp/temp.php

For instance, with this script :

$data = array('a', 'b', 'c', 'd');
while ($item = current($data))
{
    echo '<ATTR>',$item, '</ATTR>', "\n";
    next($data);
}

You will get this bytecode dump :

line     #  op                           fetch          ext  return  operands
-------------------------------------------------------------------------------
   8     0  EXT_STMT
         1  INIT_ARRAY                                       ~0      'a'
         2  ADD_ARRAY_ELEMENT                                ~0      'b'
         3  ADD_ARRAY_ELEMENT                                ~0      'c'
         4  ADD_ARRAY_ELEMENT                                ~0      'd'
         5  ASSIGN                                                   !0, ~0
   9     6  EXT_STMT
         7  EXT_FCALL_BEGIN
         8  SEND_REF                                                 !0
         9  DO_FCALL                                      1          'current'
        10  EXT_FCALL_END
        11  ASSIGN                                           $3      !1, $2
        12  JMPZ                                                     $3, ->24
  11    13  EXT_STMT
        14  ECHO                                                     '%3CATTR%3E'
        15  ECHO                                                     !1
        16  ECHO                                                     '%3C%2FATTR%3E'
        17  ECHO                                                     '%0A'
  12    18  EXT_STMT
        19  EXT_FCALL_BEGIN
        20  SEND_REF                                                 !0
        21  DO_FCALL                                      1          'next'
        22  EXT_FCALL_END
  13    23  JMP                                                      ->7
  37    24  RETURN                                                   1
        25* ZEND_HANDLE_EXCEPTION

And with this script :

$data = array('a', 'b', 'c', 'd');
while ($item = current($data))
{
    echo "<ATTR>$item</ATTR>\n";
    next($data);
}

You will get :

line     #  op                           fetch          ext  return  operands
-------------------------------------------------------------------------------
  19     0  EXT_STMT
         1  INIT_ARRAY                                       ~0      'a'
         2  ADD_ARRAY_ELEMENT                                ~0      'b'
         3  ADD_ARRAY_ELEMENT                                ~0      'c'
         4  ADD_ARRAY_ELEMENT                                ~0      'd'
         5  ASSIGN                                                   !0, ~0
  20     6  EXT_STMT
         7  EXT_FCALL_BEGIN
         8  SEND_REF                                                 !0
         9  DO_FCALL                                      1          'current'
        10  EXT_FCALL_END
        11  ASSIGN                                           $3      !1, $2
        12  JMPZ                                                     $3, ->25
  22    13  EXT_STMT
        14  INIT_STRING                                      ~4
        15  ADD_STRING                                       ~4      ~4, '%3CATTR%3E'
        16  ADD_VAR                                          ~4      ~4, !1
        17  ADD_STRING                                       ~4      ~4, '%3C%2FATTR%3E%0A'
        18  ECHO                                                     ~4
  23    19  EXT_STMT
        20  EXT_FCALL_BEGIN
        21  SEND_REF                                                 !0
        22  DO_FCALL                                      1          'next'
        23  EXT_FCALL_END
  24    24  JMP                                                      ->7
  39    25  RETURN                                                   1
        26* ZEND_HANDLE_EXCEPTION

(This ouput is with PHP 5.2.6, which is the default on Ubuntu Jaunty)

In the end , you will probably notice there is not that much differences, and that it's often really just micro-optimisation ^^

What might be more interesting is to look at the differences between versions of PHP : you might seen that some operations have been optimized between PHP 5.1 and 5.2, for instance.

For more informations, you can also have a look at Understanding Opcodes

Have fun !

EDIT : adding another test :

With this code :

$attr_open = '<ATTR>';
$attr_close = '</ATTR>';
$eol = "\n";

$data = array('a', 'b', 'c', 'd');
while ($item = current($data))
{
    echo $attr_open, $item, $attr_close, $eol;
    next($data);
}

You get :

line     #  op                           fetch          ext  return  operands
-------------------------------------------------------------------------------
  19     0  EXT_STMT
         1  ASSIGN                                                   !0, '%3CATTR%3E'
  20     2  EXT_STMT
         3  ASSIGN                                                   !1, '%3C%2FATTR%3E'
  21     4  EXT_STMT
         5  ASSIGN                                                   !2, '%0A'
  23     6  EXT_STMT
         7  INIT_ARRAY                                       ~3      'a'
         8  ADD_ARRAY_ELEMENT                                ~3      'b'
         9  ADD_ARRAY_ELEMENT                                ~3      'c'
        10  ADD_ARRAY_ELEMENT                                ~3      'd'
        11  ASSIGN                                                   !3, ~3
  24    12  EXT_STMT
        13  EXT_FCALL_BEGIN
        14  SEND_REF                                                 !3
        15  DO_FCALL                                      1          'current'
        16  EXT_FCALL_END
        17  ASSIGN                                           $6      !4, $5
        18  JMPZ                                                     $6, ->30
  26    19  EXT_STMT
        20  ECHO                                                     !0
        21  ECHO                                                     !4
        22  ECHO                                                     !1
        23  ECHO                                                     !2
  27    24  EXT_STMT
        25  EXT_FCALL_BEGIN
        26  SEND_REF                                                 !3
        27  DO_FCALL                                      1          'next'
        28  EXT_FCALL_END
  28    29  JMP                                                      ->13
  43    30  RETURN                                                   1
        31* ZEND_HANDLE_EXCEPTION

And, with this one (concatenations instead of ',') :

$attr_open = '<ATTR>';
$attr_close = '</ATTR>';
$eol = "\n";

$data = array('a', 'b', 'c', 'd');
while ($item = current($data))
{
    echo $attr_open . $item . $attr_close . $eol;
    next($data);
}

you get :

line     #  op                           fetch          ext  return  operands
-------------------------------------------------------------------------------
  19     0  EXT_STMT
         1  ASSIGN                                                   !0, '%3CATTR%3E'
  20     2  EXT_STMT
         3  ASSIGN                                                   !1, '%3C%2FATTR%3E'
  21     4  EXT_STMT
         5  ASSIGN                                                   !2, '%0A'
  23     6  EXT_STMT
         7  INIT_ARRAY                                       ~3      'a'
         8  ADD_ARRAY_ELEMENT                                ~3      'b'
         9  ADD_ARRAY_ELEMENT                                ~3      'c'
        10  ADD_ARRAY_ELEMENT                                ~3      'd'
        11  ASSIGN                                                   !3, ~3
  24    12  EXT_STMT
        13  EXT_FCALL_BEGIN
        14  SEND_REF                                                 !3
        15  DO_FCALL                                      1          'current'
        16  EXT_FCALL_END
        17  ASSIGN                                           $6      !4, $5
        18  JMPZ                                                     $6, ->30
  26    19  EXT_STMT
        20  CONCAT                                           ~7      !0, !4
        21  CONCAT                                           ~8      ~7, !1
        22  CONCAT                                           ~9      ~8, !2
        23  ECHO                                                     ~9
  27    24  EXT_STMT
        25  EXT_FCALL_BEGIN
        26  SEND_REF                                                 !3
        27  DO_FCALL                                      1          'next'
        28  EXT_FCALL_END
  28    29  JMP                                                      ->13
  43    30  RETURN                                                   1
        31* ZEND_HANDLE_EXCEPTION

So, never much of a difference ^^

Pascal MARTIN
Thanks! So what happens if, before you get to the loop, you throw '<ATTR>' into a variable and '</ATTR>\n' into another variable, and then concat them using the comma version the questioner used? That's kind of what I wanted to see...
Yar
I've added two more tests + opcodes ; not much of a difference each time. (it's the third time I try to add this comment to my own answer, and it seems to always go to the post under my response... odd ; won't try a fourth time, so sorry if this comment is not attached to *my* answer -- it should be)
Pascal MARTIN
+1  A: 

If you really want to speed this up, use this instead:

ob_start();
while ($item = current($data))
{
    echo '<ATTR>',$item, '</ATTR>', "\n";
    next($data);
}

Output buffering flushes content more efficiently to the client, which speeds up your code much more than any micro-optimization can.

As an aside, in my experience micro-optimization is a useless endeavour when it comes to PHP code. I've never seen a performance problem get solved by clever use of a particular concatenation or variable declaration method. Real solutions tend to involve change to design or architecture or the use of less complicated algorithms.

Joeri Sebrechts
True that about use of ob_start: my point, though not evident, was that PHP newbies (and veterans, sometimes) tend to avoid variable creation because it looks slower. I just tested in Ruby and using variables for things that are not going to change during the loop speeds up the loop (a bit, but it sure doesn't hurt).
Yar
Oh and +1 on the microoptimization thing. It's true, but using variables makes code clearer and it's generally better, hence I'm for it 2x.
Yar