Phix Optimizations

Small-scale optimizations are fairly easy on a language as dynamic as PHP, however larger scale ones are generally impossible or limited to some light JIT optimizations.

With the addition oh |Phixed PHP, we can enforce some requirements that make not only optimization a lot more effective, but also help catch those annoying errors PHP allows us to introduce so easily.

Reference for IA32 and IA64: http://www.intel.com/products/processor/manuals/index.htm

Array SSA & Aliasing

It would be nice is we can optimise this out:

 
  function xxx()
  {
 
    $key = 0;
 
    $arr0 = array(&$key);
 
    $arr1 = array();
 
    $data = &$arr1;
 
    $key = &$arr0[0];
 
    $data[0] = &$key;
    $data[1] = 1;
    $data[2] = 2;
    $data[4] = 2;
 
    for (array(0, 1, 4) as $key =>$val)
    {
      $data[$key] = 1;
    }
 
    return $data[0]; // aliasing rules, SSA, and loop unrolling should factor ALL of the above out, and just replace with int(1)
 
  }
 

Array optimization is actually a lot more simple than we’d expect, as they are passed around by value unless dereferenced. However, as with all things PHP, we can’t tell if a function call that is unknown at runtime is going to take it’s parameter by reference or value, thus blowing away any optimization we can do if a function that is not defined in the compile unit is called, unless we’re not in the global scope.

Drop unused calculations

expr || expr;

Here, we don’t actually care about the return value of the second expression, or indeed the ensure expression statement (boolean_or). Should drop both somehow!

Luckily, because a boolean or expression is only evaulated in order as each fails, this is executed like this:

 
e1 || e2;
 
(!e1) ? /* e1 ret */ : e2;
 

rewrite negated expressions

check out mr boolean’s logic.

(!x) is the same as (x == false)

COW (Copy on Write)

For strings.

De Morgan's laws

Apply the laws to expressions and optimize one side.

Pre-calcualte array allocation sizes

We will often know the size an array is going to initially be when creating it, for example:

$array = array( 1 => 'Theo', 2 => 'Ian', 3 => 'Adam' );

We know here that the arrya is goign to have exactly 3 elements, which means that we can allocate the memory for all 3 elements immediatly rather than as they’re allocated (ok, so 3 isn’t a good example :))

Remove unneeded try-catch blocks

In the below code, we can be sure that an exception is not going to be thrown by the moo() function (unless the interpretter throws one for us), so

<?php
 
function moo()
{
  return 1;
}
 
try
{
  moo();
}
catch (Exception $e)
{
  die("xxx");
}
 

Perform Compile-Time evaluations where possible (lazy or JIT?)

Being able to execute some stuff at compile time would be really nice and massivly reduce runtime speed in situations where the calcualtion is gogin to be performed. However, there are situations where the calcuation is not going to be performed at run time, so calcuatign at compile time is pointless. For example, lets imagine that we are calculating a mandlebrot:

 
function mandelbrot()
{
  $d1 = time();
  for ($y = -39; $y <= 39; ++$y)
  {
    echo "\n";
    for ($x = -39; $x <= 39; $x++)
    {
      echo (iterate($x / 40.0, $y / 40.0) == 0) ? '*' : ' ';
    }
  }
  $d2 = time();
  $diff = $d2 - $d1;
  echo "\n", "Elapsed: ", $diff, "\n";
}
 
function iterate($x, $y)
{
  $cr = $y - 0.5;
  $ci = $x;
  $zi = 0.0;
  $zr = 0.0;
  $i  = 0;
  while (true)
  {
    $i++;
    $temp = $zr * $zi;
    $zr2  = $zr * $zr;
    $zi2  = $zi * $zi;
    $zr   = $zr2 - $zi2 + $cr;
    $zi   = $temp + $temp + $ci;
    if ($zi2 + $zr2 > 16)
    {
      return $i;
    }
    if ($i > 1000)
    {
      return 0;
    }
  }
}
 
 
mandelbrot();
 

there are a few things to notice about this code:

  • time() will always need to be evaluated at runtime
  • iterate($x, $y) will always give the same result given the same $x and $y values
  • The two for loops in the mandlebrot() function could potentially be unrolled
  • after unrolling, we will know the values of $x and $y at compile time, as they will essentially be constant, calling instead iterate(-39 / 40.0, -39 / 40.0)
  • -39 / 40.0 can be calculated at runtime to instead push just
  • iterate(-0.975, -0.975) can be inlined.

The iterate function can be altered as follows, as it does not call any other dynamic functions, and onyl dependso n it’s input (which we know alreayd)

  $cr = $y - 0.5;
  $ci = $x;
  $zi = 0.0;
  $zr = 0.0;
  $i  = 0;
 
  while (true)
  {
 
    $i++; // note that $i is only modified here in the loop
 
    $temp = $zr * $zi;
 
    $zr2  = $zr * $zr;
    $zi2  = $zi * $zi;
 
    $zr   = $zr2 - $zi2 + $cr;
 
    $zi   = $temp + $temp + $ci;
 
    if ($zi2 + $zr2 > 16)
    {
      return $i;
    }
 
    if ($i > 1000) // loop is limited to 1000 iterations
    {
      return 0;
    }
 
  }

rewrite #1:

 
  $cr = $y - 0.5;
  $ci = $x;
  $zi = 0.0;
  $zr = 0.0;
 
  for ($i = 1 ; $i < 1000 ; ++$i)
  {
 
    $temp = $zr * $zi;
 
    $zr2  = $zr * $zr;
    $zi2  = $zi * $zi;
 
    $zr   = $zr2 - $zi2 + $cr;
 
    $zi   = $temp + $temp + $ci;
 
    if ($zi2 + $zr2 > 16)
    {
      return $i;
    }
 
  }
 
  return 0;
 
  $cr = $y - 0.5;
  $ci = $x;
  $zi = 0.0;
  $zr = 0.0;
 
  ## Iteration 1
 
  $temp = $zr * $zi;
  $zr2  = $zr * $zr;
  $zi2  = $zi * $zi;
  $zr   = $zr2 - $zi2 + $cr;
  $zi   = $temp + $temp + $ci;
 
  return 1 if ($zi2 + $zr2 > 16);
 
  ## Iteration 2
 
  $temp = $zr * $zi;
  $zr2  = $zr * $zr;
  $zi2  = $zi * $zi;
  $zr   = $zr2 - $zi2 + $cr;
  $zi   = $temp + $temp + $ci;
 
  return 2 if ($zi2 + $zr2 > 16);
 
  ## Iteration 3 ... 1000
 
  $temp = $zr * $zi;
  $zr2  = $zr * $zr;
  $zi2  = $zi * $zi;
  $zr   = $zr2 - $zi2 + $cr;
  $zi   = $temp + $temp + $ci;
 
  return 1000 if ($zi2 + $zr2 > 16);
 
  return 0;
 

Now here we need to ask pookmeister how to optimize more using his l33t hardcore math skillz :)

Runtime Results

Obviously calculating a the mandlebrot at compile time would cause the worlds slowest compile (although we could do it with loop unrolling) - however instead of that, we allow the BytePack to be monitored to see execution results. When a non-dynamic function is called with constant arguments (like our iteration($x, $y) function), the motniro can re-write the iteration function call in-line to justp ush the results, and nothing else!

So the following ByteCode:

  • OpLoadString [iterate$$1]
  • OpLoadFloat [ -0.975 ]
  • OpLoadFloat [ -0.975 ]
  • OpInvoke

can instead be re-written to:

  • OpLoadInt [ 4 ]

We can also store the result of a call iterate$$1(-0.975, -0.975) is always equal to 4 - if anything else tries to call it, we re-write the call with that if possible.

Downsides

We need to analyze the complexity of a function call to see if it is worth all the overhead of re-writing the call and storing what could be potentially a huge lookup table.

Perhaps monitoring the number of times a function is called with a given set of arguments, along with the cost of the call would allow us to cache just X number of calls to the function.

 
optimizations.txt · Last modified: 2007/03/23 14:57 by 62.232.55.74
 
Recent changes RSS feed Creative Commons License Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki