By stumbling on this so thread i decided to write similar test in PHP.
My test code is this:
// Slow version
$t1 = microtime(true);
for ($n = 0, $i = 0; $i < 20000000; $i++) {
$n += 2 * ($i * $i);
}
$t2 = microtime(true);
echo "n={$n}\n";
// Optimized version
$t3 = microtime(true);
for ($n = 0, $i = 0; $i < 20000000; $i++) {
$n += $i * $i;
}
$n *= 2;
$t4 = microtime(true);
echo "n={$n}\n";
$speedup = round(100 * (($t2 - $t1) - ($t4 - $t3)) / ($t2 - $t1), 0);
echo "speedup: {$speedup}%\n";
Results
in PHP 2 * ($i * $i) version runs quite similar like 2 * $i * $i,
so PHP interpreter isn't optimizing bytecode as JVM in Java
Even when I optimized code manually - I've got ~ 8% speedup, when
Java's version gets ~ 16% speedup. So PHP version gets about 1/2 speedup factor of that in Java's code.
Rationale for optimization
I will not go into many details, but ratio of multiplications in optimized and un-optimized code is ->
1 summation : 3/4
2 summations: 4/6
3 summations: 5/8
4 summations: 6/10
...
And in general:
where n is number of summations in a loop. To be formula useful to us - we need to calculate limit of it when N approaches infinity (to replicate situation that we do A LOT of summations in a loop). So :
So we get conclusion that in optimized code there must be 50% less multiplications.
Questions
Why PHP interpreter isn't applying code optimization ?
Why PHP speedup factor is just half of that in Java ?
It's time to analyze PHP opcodes which are generated by PHP interpreter. For that you need to install VLD extension and use it from command line to generate opcodes of php script at hand.
Opcode analysis
Seems that $i++ is not the same as ++$i in terms of opcodes and memory usage. Statement $i++; generates opcodes:
POST_INC ~4 !1
FREE ~4
Increases counter by 1 and saves previous value into memory slot #4. Then, because this value is never used - frees it from memory. Question - why do we need to store value if it is never used ?
Seems that indeed there is a loop penalty, so we can gain additional performance by performing loop unrolling.
Optimized test code
Changing POST_INC into ASSIGN_ADD (which don't saves additional info in memory) and performing loop unrolling, gives use such test code :
while (true) {
// Slow version
$t1 = microtime(true);
for ($n = 0, $i = 0; $i < 2000; $i+=10) {
// loop unrolling
$n += 2 * (($i+0) * ($i+0));
$n += 2 * (($i+1) * ($i+1));
$n += 2 * (($i+2) * ($i+2));
$n += 2 * (($i+3) * ($i+3));
$n += 2 * (($i+4) * ($i+4));
$n += 2 * (($i+5) * ($i+5));
$n += 2 * (($i+6) * ($i+6));
$n += 2 * (($i+7) * ($i+7));
$n += 2 * (($i+8) * ($i+8));
$n += 2 * (($i+9) * ($i+9));
}
$t2 = microtime(true);
echo "{$n}\n";
// Optimized version
$t3 = microtime(true);
for ($n = 0, $i = 0; $i < 2000; $i+=10) {
// loop unrolling
$n += ($i+0) * ($i+0);
$n += ($i+1) * ($i+1);
$n += ($i+2) * ($i+2);
$n += ($i+3) * ($i+3);
$n += ($i+4) * ($i+4);
$n += ($i+5) * ($i+5);
$n += ($i+6) * ($i+6);
$n += ($i+7) * ($i+7);
$n += ($i+8) * ($i+8);
$n += ($i+9) * ($i+9);
}
$n *= 2;
$t4 = microtime(true);
echo "{$n}\n";
$speedup = round(100 * (($t2 - $t1) - ($t4 - $t3)) / ($t2 - $t1), 0);
$table[$speedup]++;
echo "****************\n";
foreach ($table as $s => $c) {
if ($s >= 0 && $s <= 20)
echo "$s,$c\n";
}
}
Results
Script aggregates number of times CPU hit into one or other speedup value.
When CPU hits vs Speedup is drawn as a graph, we get such picture:
So it is most likely that script will get 10% speedup. This means that our optimizations resulted in +2% speedup (compared to original scripts 8%).
Expectations
I'm pretty sure that all these things i've done - could be done automatically by a PHP JIT'er. I don't think that it's hard to change automatically a pair of POST_INC/FREE opcodes into one PRE_INC opcode when generating binary executable. Also it's not a miracle that PHP JIT'er could apply loop unrolling. And this is just a start of optimizations !
Hopefully there will be a JIT'er in PHP 8.0
Related
Okay this is just something me and my coworker are playing with.
We know PHP has it's own PI function but this came forth out of a theory and curiosity.
So we were wondering if and how PHP was able to calculate pi.
Formule of pi = π= 4/1 - 4/3 + 4/5 - 4/7 + 4/9...
Here is what we did:
$theValue = 100;// the max
for ($i=1; $i<$theValue; $i++){
if ($i % 2 == 1){
$iWaardes[] = 4 / $i; // divide 4 by all uneven numbers and store them in an array
}
}
// Use the array's $keys as incrementing numbers to calculate the $values.
for ($a=0, $b=1, $c=2; $a<$theValue; $a+=3, $b+=3, $c+=3 ){
echo ($iWaardes[$a] - $iWaardes[$b] + $iWaardes[$c]).'<br>';
}
So now we have a loop that calculated the first series of 4/1 - 4/3 + 4/5 but it stops after that and starts over with the following 3 sequences.
How can we make it run the entire $theValue and calculate the whole series?
Please keep in mind that this is nothing serious and just a fun experiment for us.
You're overthinking this. Just use the modulo to decide if you want to add or subtract and do it in place.
$theValue = 100;// the max
$pi = 0;
for ($i=1; $i<$theValue; $i++){
if ($i % 2 == 1){
$pi += 4.0 / ($i * 2 - 1);
} else {
$pi -= 4.0 / ($i * 2 - 1);
}
}
Just use one loop. Have a $bottom variable that you add 2 on each iteration, divide by it, and add it/subtract it depending on the modulo:
$theValue = 10000; // the max
$bottom = 1;
$pi = 0;
for ($i = 1; $i < $theValue; $i++) {
if ($i % 2 == 1) {
$pi += 4 / $bottom;
} else {
$pi -= 4 / $bottom;
}
$bottom += 2;
}
var_dump($pi); // 3.14169266359
Demo
What's wrong with your code (other than not dividing by the appropriate number) is the second loop. You're for some reason printing out the stored numbers 3 by 3. This, until $a, that increases by 3, is lower than $theValue which is much higher. So, for example, if $theValue is 10, you only need 2 loops before you start getting out of bound errors.
pi() Returns an approximation of pi. The returned float has a precision based on the precision directive in php.ini, which defaults to 14. Also, you can use the M_PI constant which yields identical results to pi()
source
Using PHP we can also calculate Pi, albeit very slowly.
$pi = 4; $top = 4; $bot = 3; $minus = TRUE;
$accuracy = 1000000;
for($i = 0; $i < $accuracy; $i++)
{
$pi += ( $minus ? -($top/$bot) : ($top/$bot) );
$minus = ( $minus ? FALSE : TRUE);
$bot += 2;
}
print "Pi ~=: " . $pi;
This method of calculating Pi is slow, but it is easy to read code.
You can read more about this method here:
http://en.wikipedia.org/wiki/Leibniz_formula_for_%CF%80
If you increase the $accuracy variable, Pi will be calculated more and more accurately. Depending on how fast your web server is, you can calculate the first 6 digits of Pi fairly quickly.
The time it takes to calculate each succeeding number goes up exponentially however. To calculate 20 digits of Pi using this method method could take years.
I wrote a simple code to calculate a math equation, but I would like to iterate through numbers from $start to $end. I am making a game, and this page will calculate the amount of experience it takes to reach the next level and insert it into the database. What would be the best way to iterate from $start to $end and calculate the amount of exp needed for that level?
Code:
<?php
$start = 1;
$end = 100;
$level = $start++;
$l = $level - 1;
$exp = ((40*($l * $l)) + (360 * $l));
?>
As it sits right now it calculates the first level but i cannot for the life of me figure out how to make it go through til it reaches $end.
$exp = 0;
for($level = $start; $level <= $end; $level++){
$exp += 40 * $l ** 2 + 360 * $l;
}
Actually, we can use mathematics to make this faster by generalizing the experience level required. Since your experience function is the summation of a quadratic function:
f(n)
= S[1 100] 40n^2 + 360n
= 40n (n + 1) (2n + 1) / 6 + 360n (n + 1) / 2
In PHP:
40 * $level * ($level + 1) * (2 * $level + 1) / 6 + 360 * $level * ($level + 1) / 2
Or simplify it further if you like.
This is definitely faster than calculating a loop 100 times.
If $start is not 1, simply use f(end) - f(start - 1).
You have to calculate the needed xp for every single level, so you should put the calculation code inside a loop that starts at your lowest level and proceeds until it hits the top limit / end level. You can choose from two different loop types in PHP, the for-loop and the while-loop.
Personally, I would choose the while-loop for this specific "problem", but that is a thing everyone has to decide on his own. The code for your calculator would look like this:
// Create an extra variable to store the level for which you are currently calculating the needed xp
$i = $start;
// The while-loop (do this code until the current level hits the max level as specified)
while($i <= end) {
// use $i to calculate your exp, its the current level
// Insert code here...
// then add 1 to $i and do the same again (repeat the code inside loop)
$i++;
}
Here are some links to the documentation of php:
while-loop
for-loop
OK, using the answers over at this question I was able to produce the following code to calculated the experience needed to level on a text-based RPG I am working on. I'll say from the get-go that my math skills are not great, so please "dumb it down" as much as you can.
function calcExp($L) {
if($L <= 5) {
//If Level 5 or less then just 12 exp per level
return 12*$L;
}
if($L > 1000) {
//If over level 1,000, we need to slow them down
//add an additional 500k per level required
$calc = $L - 1000;
$c2 = 500000*$calc;
return (5*$L*$L-5*$L)+$c2;
}
else {
//otherwise, calculate as follows
$exp = 5 * $L * $L - 5 * $L;
return $exp;
}
}
That returns the correct amount of experience, now I'm trying to figure out how to reverse the process so that I can checkLvl($EXPERIENCE) and have it return the level it should be at.
So then if I were to
$level = 10;
$exp = 450;
$check = checkLvl($exp);
if($check != $level) { die('Unknown Error'); }
else { echo "Success!"; }
Note: the above code is just me trying to be clear.
I'm at a loss as to how to reverse the little mess I created.
Your first five levels are only tweleve exp per level, so you could reverse and say the first 60 exp (5 * 12) should be floor($exp / 12)
Your second level range is from 6 to 1000. So if the exp is greater than 60, but less than whatever exp you need for level 1000, you'd solve $exp = 5 * $L * $L - 5 * $L for $L, which is a quadratic equation. It would look something like $L = (5 + sqrt(25 - 4 * 5 * -$exp)) / (2 * 5)
Everything else above level 1000 should solve a similar quadratic equation, $exp = (5 * $L * $L - 5 * $L) + 500000 * ($L - 1000) for $L. It would look like $L = ((5 + 500000) + sqrt((5 + 500000)^2 - 4 * 5 * (500000 - $exp))) / (2 * 5)
I am trying to find the sum of all primes below 2000000 and here is my code:
$set = 0;
for($i = 1; $i < 2000000; $i++){
if(is_prime($i)){
$set += $i;
}
}
echo $set;
is_prime is the custom function i created to find whether the number is prime or not. The problem is it is taking too much time to execute. Any way to optimize it?
Tell PHP not to time out using set_time_limit in seconds( 0 means infinite)
set_time_limit(0);
also your loop in not efficient, a prime other than 2 cannot be even , so you should be stepping up with + 2 and add 2 to the starting $set
$set = 2
for($i = 1; $i < 2000000; $i += 2)
Code:
<?php
set_time_limit(0);
$set = 2; // 2 is a prime number so must be included in the set
for($i = 1; $i < 2000000; $i += 2){
if(is_prime($i)){
$set += $i;
}
}
echo $set;
?>
I think the is_prime($i)-method is the bottleneck.
You can calculate all prime numbers (offline) up to 2000000 by using a Sieve of Eratosthenes to store all primes in 2000000 bits (or if you only store the odd numbers: 1000000) that fits in the RAM and makes is_prime($i) O(1) time.
I was overlooking some code that I had written to generate an A-Z navigation on a product page, and the method in which it was done was a for loop; using ascii octals 65-91 and PHP's chr() function. I wondered if there was a simpler and/or more efficient way of doing this, and I discovered that PHP's range() function supports alphabetical ranges.
After I wrote my test code to compare the different methods, a few questions came to mind:
Does PHP store a static array of the alphabet?
How can I profile more deeply to look below the PHP layer to see
what's happening?
I have a cachegrind of the PHP script that can be attached if necessary, in addition to environment config. For those who might want to know the machine specs in which it was executed, here are some links:
root#workbox:~$ lshw
http://pastebin.com/cZZRjJcR
root#workbox:~$ sysinfo
http://pastebin.com/ihQkkPAJ
<?php
/*
* determine which method out of 3 for returning
* an array of uppercase alphabetic characters
* has the highest performance
*
* +++++++++++++++++++++++++++++++++++++++++++++
*
* 1) Array $alpha = for($x = 65; $x < 91; $x++) { $upperChr[] = chr($x); }
* 2) Array $alpha = range(chr(65), chr(90);
* 3) Array $alpha = range('A', 'Z');
*
* +++++++++++++++++++++++++++++++++++++++++++++
*
* test runs with iterations:
*
* 10,000:
* - 1) upperChrElapsed: 0.453785s
* - 2) upperRangeChrElapsed: 0.069262s
* - 3) upperRangeAZElapsed: 0.046110s
*
* 100,000:
* - 1) upperChrElapsed: 0.729015s
* - 2) upperRangeChrElapsed: 0.078652s
* - 3) upperRangeAZElapsed: 0.052071s
*
* 1,000,000:
* - 1) upperChrElapsed: 50.942950s
* - 2) upperRangeChrElapsed: 10.091785s
* - 3) upperRangeAZElapsed: 8.073058s
*/
ini_set('max_execution_time', 0);
ini_set('memory_limit', 0);
define('ITERATIONS', 1000000); // 1m loops x3
$upperChrStart = microtime(true);
for($i = 0; $i <= ITERATIONS; $i++) {
$upperChr = array();
for($x = 65; $x < 91; $x++) {
$upperChr[] = chr($x);
}
}
$upperChrElapsed = microtime(true) - $upperChrStart;
// +++++++++++++++++++++++++++++++++++++++++++++
$upperRangeChrStart = microtime(true);
for($i = 0; $i <= ITERATIONS; $i++) {
$upperRangeChr = range(chr(65), chr(90));
}
$upperRangeChrElapsed = microtime(true) - $upperRangeChrStart;
// +++++++++++++++++++++++++++++++++++++++++++++
$upperRangeAZStart = microtime(true);
for($i = 0; $i <= ITERATIONS; $i++) {
$upperRangeAZ = range('A', 'Z');
}
$upperRangeAZElapsed = microtime(true) - $upperRangeAZStart;
printf("upperChrElapsed: %f\n", $upperChrElapsed);
printf("upperRangeChrElapsed: %f\n", $upperRangeChrElapsed);
printf("upperRangeAZElapsed: %f\n", $upperRangeAZElapsed);
?>
Does PHP waste memory keeping an array of letters? I would doubt it. range() will work on a wide variety of values too.
If performance is an issue in such a case, you might want to declare the array outside of the loop so that it can be re-used. However, large gains rarely come from micro-optimizations. Using profiling on larger applications to get significant gains.
As for profiling at a lower level, you can simply use valgrind on PHP CLI. I've also seen it used on an apache process.
Related: How to profile my C++ application on linux