Does PHP Optimize Tail Recursion

Does PHP optimize tail recursion?

Here are the generated opcodes for that (sorry for the strange representation):

Global
-------------------------------------------------------------------------------
BCDCAC 0003: NOP ()
BCDD24 0012: SEND_VAL (CONST: "500000")
BCDD9C 0012: SEND_VAL (CONST: NULL)
BCDE14 0012: DO_FCALL (CONST: "sumrand") -> VAR 0
BCDE8C 0012: CONCAT (VAR 0, CONST: "\n") -> TMP_VAR 1
BCDF04 0012: ECHO (TMP_VAR 1)
BCDF7C 0014: RETURN (CONST: "1")

Functions
-------------------------------------------------------------------------------
sumrand (17 op)
BCFABC 0003: RECV (CONST: "1") -> CV 0 ($n)
BCFB34 0003: RECV (CONST: "2") -> CV 1 ($sum)
BCFBAC 0004: IS_EQUAL (CV 0 ($n), CONST: NULL) -> TMP_VAR 0
BCFC24 0004: JMPZ (TMP_VAR 0, &(BCFD18+6))
BCFC9C 0005: RETURN (CV 1 ($sum))
BCFD14 0006: JMP (&(BD01C8+10))
BCFD8C 0008: INIT_FCALL_BY_NAME (NULL, CONST: "sumrand")
BCFE04 0008: SUB (CV 0 ($n), CONST: "1") -> TMP_VAR 1
BCFE7C 0008: SEND_VAL (TMP_VAR 1)
BCFEF4 0008: SEND_VAL (CONST: NULL)
BCFF6C 0008: SEND_VAL (CONST: "1")
BCFFE4 0008: DO_FCALL (CONST: "rand") -> VAR 2
BD005C 0008: ADD (CV 1 ($sum), VAR 2) -> TMP_VAR 3
BD00D4 0008: SEND_VAL (TMP_VAR 3)
BD014C 0008: DO_FCALL_BY_NAME () -> VAR 4
BD01C4 0008: RETURN (VAR 4)
BD023C 0010: RETURN (CONST: NULL)

So, no, it certainly doesn't seem so.

Does Tail Recursion will be an optimization in php

The second script is faster because it recurses fewer total times, not because it uses tail recursion.

(The first script calls itself twice at each level. The second script only calls itself once at each level. As such, the second script simply ends up doing far less work than the first one.)

Optimizing a Recursive Method In PHP

See if you can use tail recursion rather than call-based recursion. Some rewriting may be required but a cursory looks says it is fine to do.

Tail recursion is great for a subset of recursive functions, and good practice to spot when loops can replace recursion, and how to rewrite.

Saying that, I don't know what the overhead inside PHP is of the call. Might just be one return-pointer type setup rather than a real stack wind.

Turns out about the same. Does PHP optimize tail recursive calls out itself?

Here is my rewrite, but beware, my brain is currently sleep deprived!

protected function compilePhrase($data, $currentIndex, $depth){
/* Loop until break condition */
while(true) {
if (!empty($data)){
if ($depth >= $this->phraseStart){
$this->addDataCount($data, $depth);
}
if ($depth < $this->phraseDepth){
$currentIndex = $currentIndex + 1;
// A test here might be better than the !empty($data)
// in the IF condition. Check array bounds, assuming
// array is not threaded or anything
$data .= ' '.$this->dataArray[$currentIndex];
$depth += 1;
}else{
break;
}
}else{
break;
}
}
/* Finish up here */
return($this->dataArray);
}

Optimizing a non-tail-recursive function

This code is untested, but from the top of my head, the iterative function should look something like this:

function render($index){
$stack = array();
array_push($index);

$pre = '';
$post = '';

while(!empty($stack)){
$idx = array_pop($stack);

foreach($things[$idx] as $key => $value){
$pre .= '<1>';
$spost = '';

if(isset($data['id'])){
$pre .= '<2 class="wrap">';
$spost .= '</2>';

$stack[] = $things[$data['id']];
}

$spost .= '</1>';
$post .= $spost;
}
}

return $pre . $post;
}

Tail Recursion Slower Than Normal Recursion in PHP?

I took your code and modified it slightly for running on the command line (converted br's to newlines, tweaked the output format). Also, I changed it so that benchmark is run for 25 iterations instead of just one:

<?php
foreach (range(1, 25) as $iteration) {
benchmark($iteration);
}

function benchmark($iteration)
{
$n = 10;
$multiplier = 10000;
$functions = array('factorial_recursive', 'factorial_tailRecursive');

echo "\nIteration {$iteration}:\n";
foreach ($functions as $function) {
$start = microtime(true);
echo $function . "\n";
echo $function($n) . "\n";
echo ($multiplier * (microtime(true) - $start)) . "\n\n";
}
}

function factorial_recursive($n)
{
if ($n == 1) {
return $n;
}

return $n * factorial_recursive($n - 1);
}

function factorial_tailRecursive($n, $result = 1)
{
if ($n == 1) {
return $result;
}

return factorial_tailRecursive($n - 1, $result * $n);
}

Here is my php cli version:

$ php --version
PHP 5.3.10-1ubuntu3.4 with Suhosin-Patch (cli) (built: Sep 12 2012 19:00:43)
Copyright (c) 1997-2012 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2012 Zend Technologies
with Xdebug v2.1.0, Copyright (c) 2002-2010, by Derick Rethans

Here are the results of my 25 iterations:

$ php ./benchmark.php 

Iteration 1:
factorial_recursive
3628800
0.46014785766602

factorial_tailRecursive
3628800
0.33855438232422

Iteration 2:
factorial_recursive
3628800
0.26941299438477

factorial_tailRecursive
3628800
0.26941299438477

Iteration 3:
factorial_recursive
3628800
0.27179718017578

factorial_tailRecursive
3628800
0.2598762512207

Iteration 4:
factorial_recursive
3628800
0.26941299438477

factorial_tailRecursive
3628800
0.2598762512207

Iteration 5:
factorial_recursive
3628800
0.28848648071289

factorial_tailRecursive
3628800
0.28848648071289

Iteration 6:
factorial_recursive
3628800
0.27894973754883

factorial_tailRecursive
3628800
0.27894973754883

Iteration 7:
factorial_recursive
3628800
0.29087066650391

factorial_tailRecursive
3628800
0.2598762512207

Iteration 8:
factorial_recursive
3628800
0.25033950805664

factorial_tailRecursive
3628800
0.25033950805664

Iteration 9:
factorial_recursive
3628800
0.25033950805664

factorial_tailRecursive
3628800
0.2598762512207

Iteration 10:
factorial_recursive
3628800
0.25033950805664

factorial_tailRecursive
3628800
0.24795532226562

Iteration 11:
factorial_recursive
3628800
0.25033950805664

factorial_tailRecursive
3628800
0.27894973754883

Iteration 12:
factorial_recursive
3628800
0.27894973754883

factorial_tailRecursive
3628800
0.27894973754883

Iteration 13:
factorial_recursive
3628800
0.2598762512207

factorial_tailRecursive
3628800
0.25033950805664

Iteration 14:
factorial_recursive
3628800
0.25033950805664

factorial_tailRecursive
3628800
0.27179718017578

Iteration 15:
factorial_recursive
3628800
0.25033950805664

factorial_tailRecursive
3628800
0.24795532226562

Iteration 16:
factorial_recursive
3628800
0.24080276489258

factorial_tailRecursive
3628800
0.25033950805664

Iteration 17:
factorial_recursive
3628800
0.27179718017578

factorial_tailRecursive
3628800
0.25033950805664

Iteration 18:
factorial_recursive
3628800
0.24080276489258

factorial_tailRecursive
3628800
0.25033950805664

Iteration 19:
factorial_recursive
3628800
0.25033950805664

factorial_tailRecursive
3628800
0.25033950805664

Iteration 20:
factorial_recursive
3628800
0.25033950805664

factorial_tailRecursive
3628800
0.2598762512207

Iteration 21:
factorial_recursive
3628800
0.24795532226562

factorial_tailRecursive
3628800
0.23841857910156

Iteration 22:
factorial_recursive
3628800
0.30040740966797

factorial_tailRecursive
3628800
0.29087066650391

Iteration 23:
factorial_recursive
3628800
0.30994415283203

factorial_tailRecursive
3628800
0.26226043701172

Iteration 24:
factorial_recursive
3628800
0.29087066650391

factorial_tailRecursive
3628800
0.32186508178711

Iteration 25:
factorial_recursive
3628800
0.27894973754883

factorial_tailRecursive
3628800
0.30040740966797

Looking at these results, I'd have to say the difference is negligible, too close to call. They're effectively identical in runtime, at least in terms of Big-O.

How can I make this function tail recursive?

This would not be an example of tail recursion because computation is still being done in the earlier recursive calls AFTER the value is return. Consider the example provided in a previous StackOverflow post (What is tail recursion?). In this case, the ".=" operator would complete a concatenation and THEN return the result in the function.

To make this tail recursive you would want to modify your code print the 'output' variable prior to making the recursive call

Does array_walk_recursive use tail call optimization?

Short answer, no :)

See also: https://github.com/php/php-src/blob/master/ext/standard/array.c#L1097

What is tail recursion?

Consider a simple function that adds the first N natural numbers. (e.g. sum(5) = 0 + 1 + 2 + 3 + 4 + 5 = 15).

Here is a simple JavaScript implementation that uses recursion:

function recsum(x) {
if (x === 0) {
return 0;
} else {
return x + recsum(x - 1);
}
}

If you called recsum(5), this is what the JavaScript interpreter would evaluate:

recsum(5)
5 + recsum(4)
5 + (4 + recsum(3))
5 + (4 + (3 + recsum(2)))
5 + (4 + (3 + (2 + recsum(1))))
5 + (4 + (3 + (2 + (1 + recsum(0)))))
5 + (4 + (3 + (2 + (1 + 0))))
5 + (4 + (3 + (2 + 1)))
5 + (4 + (3 + 3))
5 + (4 + 6)
5 + 10
15

Note how every recursive call has to complete before the JavaScript interpreter begins to actually do the work of calculating the sum.

Here's a tail-recursive version of the same function:

function tailrecsum(x, running_total = 0) {
if (x === 0) {
return running_total;
} else {
return tailrecsum(x - 1, running_total + x);
}
}

Here's the sequence of events that would occur if you called tailrecsum(5), (which would effectively be tailrecsum(5, 0), because of the default second argument).

tailrecsum(5, 0)
tailrecsum(4, 5)
tailrecsum(3, 9)
tailrecsum(2, 12)
tailrecsum(1, 14)
tailrecsum(0, 15)
15

In the tail-recursive case, with each evaluation of the recursive call, the running_total is updated.

Note: The original answer used examples from Python. These have been changed to JavaScript, since Python interpreters don't support tail call optimization. However, while tail call optimization is part of the ECMAScript 2015 spec, most JavaScript interpreters don't support it.

Are algorithm benchmarks in PHP pointless?

It's absolutely not pointless to benchmark algorithms in a scripting language. After doing the benchmarks, which implementation of factorial would you use in PHP? (assuming that you couldn't use the builtin one for some reason.)

It is fairly pointless to benchmark in a language that has significantly different features than the one that you want to implement the algorithm in though. Here, the relative cost of function calls and if statements in PHP is skewing the results significantly (or this is my best guess anyways). If you are careful to understand why this is happening and avoid it, it can still be fruitful though: differences will be more exaggerated as you noticed. It comes down to if it's easier to write it in the target language or work around the differences.

A simple calculation of complexity of the algorithm should be enough to decide which one to use, or at least narrow down the selections.


As Mike Axiak points out in the comments, you are not even testing different algorithms here, you are testing two different implementations of the same algorithm: keep a running product over i from n to 1. Doing this in a different language than the target is almost always pointless.



Related Topics



Leave a reply



Submit