3

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.)

Community
  • 1
  • 1
Dan Rosenstark
  • 68,471
  • 58
  • 283
  • 421

6 Answers6

3

Here is 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);
}
peterh
  • 11,875
  • 18
  • 85
  • 108
Kane Wallmann
  • 2,292
  • 15
  • 10
  • 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 Jul 21 '09 at 02:40
  • You would probably get better speed if you put the newline in with the closing tag - "\n" – too much php Jul 21 '09 at 03:02
  • And my original guess was that even putting "" 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! – Dan Rosenstark Jul 21 '09 at 09:14
3

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
  • 395,085
  • 80
  • 655
  • 663
  • Thanks! So what happens if, before you get to the loop, you throw '' into a variable and '\n' into another variable, and then concat them using the comma version the questioner used? That's kind of what I wanted to see... – Dan Rosenstark Jul 21 '09 at 09:17
  • 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 Jul 21 '09 at 17:23
2

There seems to be no measurable difference in using the string literals inside the loop vs. moving them to variables outside the loop. I threw together the following simple script to test this:

$length = 100000;
$data = array();
$totals = array();

for ($i = 0; $i < $length; $i++) {
    $data[] = rand(1,1000);
}

$start = xdebug\_time\_index();
while ($item = current($data))
{
    echo '<ATTR>',$item,'</ATTR>',PHP_EOL;
    next($data);
}
$end = xdebug\_time\_index();
$total = $end - $start;
$totals["Warmup:"] = $total;

reset($data);
$start = xdebug\_time\_index();
while ($item = current($data))
{
    echo '<ATTR>',$item,'</ATTR>',PHP_EOL;
    next($data);
}
$end = xdebug\_time\_index();
$total = $end - $start;
$totals["First:"] = $total;

reset($data);
$startTag = '<ATTR>';
$endTag = '</ATTR>';
$start = xdebug\_time\_index();
while ($item = current($data))
{
    echo $startTag,$item,$endTag,PHP_EOL;
    next($data);
}
$end = xdebug\_time\_index();
$total = $end - $start;
$totals["Second:"] = $total;

foreach ($totals as $label => $data) {
    echo $label,' ', $data,PHP_EOL;
}

I ran this several times and saw no discernable difference between the differing methods. In fact, sometimes the warmup was the fastest of the three.

When trying to microoptimize things such as this you really end up measuring the performance of the machine you are on more often than the actual code. Of note, you may want to use PHP_EOL instead of \n or defining a variable containing such.

Jason Plank
  • 2,336
  • 5
  • 31
  • 40
hobodave
  • 28,925
  • 4
  • 72
  • 77
  • 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... – Dan Rosenstark Jul 21 '09 at 09:22
1

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
  • 95
  • 1
  • 6
  • Really? I've been operating under the assumption that concat is a relatively low cost operation in PHP, given its mutable strings and all. – Alana Storm Jul 21 '09 at 05:07
  • 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., ) each time around. – Dan Rosenstark Jul 21 '09 at 09:18
1

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.

Community
  • 1
  • 1
Fake Code Monkey Rashid
  • 13,731
  • 6
  • 37
  • 41
  • 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. – Dan Rosenstark Jul 21 '09 at 09:19
1

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
  • 11,012
  • 3
  • 35
  • 50
  • 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). – Dan Rosenstark Jul 21 '09 at 17:23
  • 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. – Dan Rosenstark Jul 21 '09 at 17:26