Viewed   66 times

(assume php5) consider

<?php

    $foo = 'some words';

    //case 1
    print "these are $foo";

    //case 2
    print "these are {$foo}";

    //case 3
    print 'these are ' . $foo;
?>

Is there much of a difference between 1 and 2?

If not, what about between 1/2 and 3?

 Answers

4

Well, as with all "What might be faster in real life" questions, you can't beat a real life test.

function timeFunc($function, $runs)
{
  $times = array();

  for ($i = 0; $i < $runs; $i++)
  {
    $time = microtime();
    call_user_func($function);
    $times[$i] = microtime() - $time;
  }

  return array_sum($times) / $runs;
}

function Method1()
{ 
  $foo = 'some words';
  for ($i = 0; $i < 10000; $i++)
    $t = "these are $foo";
}

function Method2()
{
  $foo = 'some words';
  for ($i = 0; $i < 10000; $i++)
    $t = "these are {$foo}";
}

function Method3()
 {
  $foo = 'some words';
  for ($i = 0; $i < 10000; $i++)
    $t = "these are " . $foo;
}

print timeFunc('Method1', 10) . "n";
print timeFunc('Method2', 10) . "n";
print timeFunc('Method3', 10) . "n";

Give it a few runs to page everything in, then...

0.0035568

0.0035388

0.0025394

So, as expected, the interpolation are virtually identical (noise level differences, probably due to the extra characters the interpolation engine needs to handle). Straight up concatenation is about 66% of the speed, which is no great shock. The interpolation parser will look, find nothing to do, then finish with a simple internal string concat. Even if the concat were expensive, the interpolator will still have to do it, after all the work to parse out the variable and trim/copy up the original string.

Updates By Somnath:

I added Method4() to above real time logic.

function Method4()
 {
  $foo = 'some words';
  for ($i = 0; $i < 10000; $i++)
    $t = 'these are ' . $foo;
}

print timeFunc('Method4', 10) . "n";

Results were:

0.0014739
0.0015574
0.0011955
0.001169

When you are just declaring a string only and no need to parse that string too, then why to confuse PHP debugger to parse. I hope you got my point.

Wednesday, October 12, 2022
4

Array access in PHP can certainly be slow. PHP uses hash tables to implement arrays, i.e. in order to access an element in an array it has to calculate a hash and traverse a linked list. Using a compiled language with real arrays will definitely improve performance, because there a direct memory access is made. For the interested: Code for hash access with string and with integer.

Concerning your code, there are several points I would optimize:

  • return directly, don't break twice.
  • put $file->get_width() and $file->get_height into simple variables. I assume that the height or width doesn't change throughout the process. Remember: Functions in PHP are slow.
  • Use a one-dimensional array, instead of nested arrays. You save one hash lookup per iteration that way. Actually a one-dimensional array is only marginally faster or even slightly slower. Comparison of several ways of saving the data concerning performance and memory usage.

.

function fits($bin, $x, $y, $w, $h) {
    $w += $x;
    $h += $y;

    for ($i = $x; $i < $w; ++$i) {
        for ($j = $y; $j < $h; ++$j) {
            if ($bin[$i][$j] !== 0) {
                return false;
            }
        } 
    }

    return true;   
}

Though I'm not sure, why you add $x to the $width / $y to the $height. Don't you want to iterate from the current coordinates to the image boundaries?

Thursday, November 10, 2022
 
3

You won't replicate this in PHP. End of story. The reason is that the Java RTS uses JiT compilation techniques to compile the Java intermediate code down to the underlying X86 order code. This underlying order code will expose these branch prediction artefacts.

The PHP runtime system compiles PHP down to a bytecode which is a pseudo machine code that is interpreted. This interpreter will execute of the order of 0.5M opcodes /sec on a typical single core -- that is each PHP opcode takes perhaps 2-6K native instructions. Any subtleties of branching will be lost in this.

Monday, September 12, 2022
 
roddy
 
3

The problem in

((((a ++ b) ++ c) ++ d) ++ e) ++ f

is the nesting. The applications of (++) are left-nested, and that's bad; right-nesting

a ++ (b ++ (c ++ (d ++ (e ++f))))

would not be a problem. That is because (++) is defined as

[] ++ ys = ys
(x:xs) ++ ys = x : (xs ++ ys)

so to find which equation to use, the implementation must dive into the expression tree

             (++)
             /  
          (++)   f
          /  
       (++)   e
       /  
    (++)   d
    /  
 (++)   c
 /  
a    b

until it finds out whether the left operand is empty or not. If it's not empty, its head is taken and bubbled to the top, but the tail of the left operand is left untouched, so when the next element of the concatenation is demanded, the same procedure starts again.

When the concatenations are right-nested, the left operand of (++) is always at the top, and checking for emptiness/bubbling up the head are O(1).

But when the concatenations are left-nested, n layers deep, to reach the first element, n nodes of the tree must be traversed, for each element of the result (coming from the first list, n-1 for those coming from the second etc.).

Let us consider a = "hello" in

hi = ((((a ++ b) ++ c) ++ d) ++ e) ++ f

and we want to evaluate take 5 hi. So first, it must be checked whether

(((a ++ b) ++ c) ++ d) ++ e

is empty. For that, it must be checked whether

((a ++ b) ++ c) ++ d

is empty. For that, it must be checked whether

(a ++ b) ++ c

is empty. For that, it must be checked whether

a ++ b

is empty. For that, it must be checked whether

a

is empty. Phew. It isn't, so we can bubble up again, assembling

a ++ b                             = 'h':("ello" ++ b)
(a ++ b) ++ c                      = 'h':(("ello" ++ b) ++ c)
((a ++ b) ++ c) ++ d               = 'h':((("ello" ++ b) ++ c) ++ d)
(((a ++ b) ++ c) ++ d) ++ e        = 'h':(((("ello" ++ b) ++ c) ++ d) ++ e)
((((a ++ b) ++ c) ++ d) ++ e) ++ f = 'h':((((("ello" ++ b) ++ c) ++ d) ++ e) ++ f)

and for the 'e', we must repeat, and for the 'l's too...

Drawing a part of the tree, the bubbling up goes like this:

            (++)
            /  
         (++)   c
         /  
'h':"ello"   b

becomes first

     (++)
     /  
   (:)   c
  /   
'h'   (++)
      /  
 "ello"   b

and then

      (:)
      / 
    'h' (++)
        /  
     (++)   c
     /  
"ello"   b

all the way back to the top. The structure of the tree that becomes the right child of the top-level (:) finally, is exactly the same as the structure of the original tree, unless the leftmost list is empty, when the

 (++)
 /  
[]   b

nodes is collapsed to just b.

So if you have left-nested concatenations of short lists, the concatenation becomes quadratic because to get the head of the concatenation is an O(nesting-depth) operation. In general, the concatenation of a left-nested

(...((a_d ++ a_{d-1}) ++ a_{d-2}) ...) ++ a_2) ++ a_1

is O(sum [i * length a_i | i <- [1 .. d]]) to evaluate fully.

With difference lists (sans the newtype wrapper for simplicity of exposition), it's not important whether the compositions are left-nested

((((a ++) . (b ++)) . (c ++)) . (d ++)) . (e ++)

or right-nested. Once you have traversed the nesting to reach the (a ++), that (++) is hoisted to the top of the expression tree, so getting at each element of a is again O(1).

In fact, the whole composition is reassociated with difference lists, as soon as you require the first element,

((((a ++) . (b ++)) . (c ++)) . (d ++)) . (e ++) $ f

becomes

((((a ++) . (b ++)) . (c ++)) . (d ++)) $ (e ++) f
(((a ++) . (b ++)) . (c ++)) $ (d ++) ((e ++) f)
((a ++) . (b ++)) $ (c ++) ((d ++) ((e ++) f))
(a ++) $ (b ++) ((c ++) ((d ++) ((e ++) f)))
a ++ (b ++ (c ++ (d ++ (e ++ f))))

and after that, each list is the immediate left operand of the top-level (++) after the preceding list has been consumed.

The important thing in that is that the prepending function (a ++) can start producing its result without inspecting its argument, so that the reassociation from

             ($)
             / 
           (.)  f
           / 
         (.) (e ++)
         / 
       (.) (d ++)
       / 
     (.) (c ++)
     / 
(a ++) (b ++)

via

           ($)---------
           /           
         (.)           ($)
         /            / 
       (.) (d ++) (e ++)  f
       / 
     (.) (c ++)
     / 
(a ++) (b ++)

to

     ($)
     / 
(a ++) ($)
       / 
  (b ++) ($)
         / 
    (c ++) ($)
           / 
      (d ++) ($)
             / 
        (e ++)  f

doesn't need to know anything about the composed functions of the final list f, so it's just an O(depth) rewriting. Then the top-level

     ($)
     / 
(a ++)  stuff

becomes

 (++)
 /  
a    stuff

and all elements of a can be obtained in one step. In this example, where we had pure left-nesting, only one rewriting is necessary. If instead of (for example) (d ++) the function in that place had been a left-nested composition, (((g ++) . (h ++)) . (i ++)) . (j ++), the top-level reassociation would leave that untouched and this would be reassociated when it becomes the left operand of the top-level ($) after all previous lists have been consumed.

The total work needed for all reassociations is O(number of lists), so the overall cost for the concatenation is O(number of lists + sum (map length lists)). (That means you can bring bad performance to this too, by inserting a lot of deeply left-nested ([] ++).)

The

newtype DiffList a = DiffList {getDiffList :: [a] -> [a] }

instance Monoid (DiffList a) where
    mempty = DiffList (xs -> [] ++ xs)
    (DiffList f) `mappend` (DiffList g) = DiffList (xs -> f (g xs))

just wraps that so that it is more convenient to handle abstractly.

DiffList (a ++) `mappend` DiffList (b ++) ~> DiffList ((a ++) . (b++))

Note that it is only efficient for functions that don't need to inspect their argument to start producing output, if arbitrary functions are wrapped in DiffLists, you have no such efficiency guarantees. In particular, appending ((++ a), wrapped or not) can create left-nested trees of (++) when composed right-nested, so you can create the O(n²) concatenation behaviour with that if the DiffList constructor is exposed.

Friday, November 25, 2022
 
dronus
 
5

They are equivalent, even if you turn the optimizer off.

Example:

#include <stdio.h>

extern void f(void) {
    while(1) {
        putchar(' ');
    }
}

extern void g(void) {
    for(;;){
        putchar(' ');
    }
}

extern void h(void) {
    z:
        putchar(' ');
    goto z;
}

Compile with gcc -O0 gives equivalent assembly for all 3 functions:

 f:
 ;  [ EXTERNAL ]
 ;
 +00000 00000fb4 80402DE9             stmdb             sp!,{r7,lr}
 +00004 00000fb8 00708DE2             add               r7,sp,#0x0
 +00008 00000fbc 2000A0E3 loc_000008: mov               r0,#0x20
 +0000c 00000fc0 0A0000EB             bl                putchar (stub)
 +00010 00000fc4 FCFFFFEA             b                 loc_000008
 ;
 ;
 g:
 ;  [ EXTERNAL ]
 ;
 +00000 00000fc8 80402DE9             stmdb             sp!,{r7,lr}
 +00004 00000fcc 00708DE2             add               r7,sp,#0x0
 +00008 00000fd0 2000A0E3 loc_000008: mov               r0,#0x20
 +0000c 00000fd4 050000EB             bl                putchar (stub)
 +00010 00000fd8 FCFFFFEA             b                 loc_000008
 ;
 ;
 h:
 ;  [ EXTERNAL ]
 ;
 +00000 00000fdc 80402DE9             stmdb             sp!,{r7,lr}
 +00004 00000fe0 00708DE2             add               r7,sp,#0x0
 +00008 00000fe4 2000A0E3 loc_000008: mov               r0,#0x20
 +0000c 00000fe8 000000EB             bl                putchar (stub)
 +00010 00000fec FCFFFFEA             b                 loc_000008
Wednesday, September 14, 2022
 
Only authorized users can answer the search term. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :