3

I tried to benchmark the short code from here converting it directly from javascript to PHP

Javascript:

(function() {
    var a = 3.1415926, b = 2.718;
    var i, j, d1, d2;
    for(j=0; j<10; j++) {
        for(i=0; i<100000000; i++) {
            a = a + b;
        }
    }
    console.log("a = " + a);
})();

PHP:

<?php

$a = 3.1415926;
$b = 2.718;

for($j=0; $j<10; $j++) {
    for($i=0; $i<100000000; $i++) {
        $a = $a + $b;
    }
}
echo "a = $a\n";

Running both on the console (my Macbook terminal, node v5.7.0 vs PHP 7.0.4), the results are quite puzzling:

$ time node test.js
a = 2717999973.76071

real    0m1.340s
user    0m0.912s
sys 0m0.021s

$ time php t.php
a = 2717999973.7607

real    2m40.239s
user    2m35.271s
sys 0m0.507s

Is really PHP in basic math operations 120 times slower than node? Is there anything that one can do to optimize this?

This

Community
  • 1
  • 1
pistacchio
  • 56,889
  • 107
  • 278
  • 420
  • 3
    It's important to remember where the code is running. With php, the resources are being pulled from your hosting server. A slow cpu and slow memory on the server will cause slow php code. Javascript math is ran from the users machine, through their browser, with an interpreter. Math in Javascript depends on the users machine speed. – jjonesdesign Apr 13 '16 at 16:01
  • 3
    @jjonesdesign As stated on the question, I'm running them both on my Macbook terminal. They're both executed as shell scripts, not browser on the client vs server on a remote host. – pistacchio Apr 13 '16 at 16:02
  • 1
    Benchmarking PHP/Javascript accurately can be quite a task in its own right, I would start by showing us how you produced those figures – RiggsFolly Apr 13 '16 at 16:04
  • 4
    I would bet that node is pre-compiling and optimizing out pretty much all that math into a much fewer math expressions. Something that PHP won't do. V8 is highly optimized to look for shortcuts like this where as php will actually run each line by line. – Jonathan Kuhn Apr 13 '16 at 16:06
  • 2
    I think @JonathanKuhn is right, and Javascript has figured out that it can convert the entire loop to `a = a + b * 1000000000;` – Barmar Apr 13 '16 at 16:23
  • 1
    Although if it really did that, I'm surprised that it took as long as 1 second. – Barmar Apr 13 '16 at 16:24
  • 1
    I would say that 1 second is fine including the compile and optimization time. They are benchmarking the entire command line call, not within node/php when the code runs. Which I assume would show a few ms of time. If they shortened that expression up to run like 1k times, it would probably show a similar time. – Jonathan Kuhn Apr 13 '16 at 16:24

2 Answers2

2

Whilst I can't account for the huge difference between javascript and PHP, I have been able to make the processing of the PHP code run about 30% faster in one small change.

After running a few tests locally, I foung changing

$a = $a + $b;

to

$a += $b;

yielded a run time of 53-55 seconds, down from a 78-80 (on my machine - i5 8GB RAM and a slow disk). Using a similar saving estimate (rounded down to 25%) should bring the processing in at around 2 minutes in total. Clearly this isn't as close to the JavaScript time.

Depending on what you realistically want to run (as benchmarking only tells you so much) there may be other improvements you can make, but not always for such large numbers.

gabe3886
  • 4,235
  • 3
  • 27
  • 31
2

The JavaScript gets compiled nearly 1:1, so 10^9 loops with not much flesh in it run as fast as the CPU allows (here: 2.14s. with an old node, version v0.10.25 ).

PHP on the other side does a lot, especially the Zend machine. If you dump the op-codes of your little program with VLD you get (php 7.0.5):

$ php -d vld.active=1 -d vld.execute=0   -f benchmark.php

Finding entry points
Branch analysis from position: 0
Jump found. Position 1 = 14
Branch analysis from position: 14
Jump found. Position 1 = 16, Position 2 = 4
Branch analysis from position: 16
Jump found. Position 1 = -2
Branch analysis from position: 4
Jump found. Position 1 = 10
Branch analysis from position: 10
Jump found. Position 1 = 12, Position 2 = 6
Branch analysis from position: 12
Jump found. Position 1 = 16, Position 2 = 4
Branch analysis from position: 16
Branch analysis from position: 4
Branch analysis from position: 6
Jump found. Position 1 = 12, Position 2 = 6
Branch analysis from position: 12
Branch analysis from position: 6
filename:       benchmark.php
function name:  (null)
number of ops:  21
compiled vars:  !0 = $a, !1 = $b, !2 = $j, !3 = $i
line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   2     0  E >   ASSIGN                                                   !0, 3.14159
   3     1        ASSIGN                                                   !1, 2.718
   5     2        ASSIGN                                                   !2, 0
         3      > JMP                                                      ->14
   6     4    >   ASSIGN                                                   !3, 0
         5      > JMP                                                      ->10
   7     6    >   ADD                                              ~8      !0, !1
         7        ASSIGN                                                   !0, ~8
   6     8        POST_INC                                         ~10     !3
         9        FREE                                                     ~10
        10    >   IS_SMALLER                                       ~11     !3, 100000000
        11      > JMPNZ                                                    ~11, ->6
   5    12    >   POST_INC                                         ~12     !2
        13        FREE                                                     ~12
        14    >   IS_SMALLER                                       ~13     !2, 10
        15      > JMPNZ                                                    ~13, ->4
  10    16    >   ROPE_INIT                                     3  ~15     'a+%3D+'
        17        ROPE_ADD                                      1  ~15     ~15, !0
        18        ROPE_END                                      2  ~14     ~15, '%0A'
        19        ECHO                                                     ~14
        20      > RETURN                                                   1

branch: #  0; line:     2-    5; sop:     0; eop:     3; out1:  14
branch: #  4; line:     6-    6; sop:     4; eop:     5; out1:  10
branch: #  6; line:     7-    6; sop:     6; eop:     9; out1:  10
branch: # 10; line:     6-    6; sop:    10; eop:    11; out1:  12; out2:   6
branch: # 12; line:     5-    5; sop:    12; eop:    13; out1:  14
branch: # 14; line:     5-    5; sop:    14; eop:    15; out1:  16; out2:   4
branch: # 16; line:    10-   10; sop:    16; eop:    20; out1:  -2
path #1: 0, 14, 16, 
path #2: 0, 14, 4, 10, 12, 14, 16, 
path #3: 0, 14, 4, 10, 6, 10, 12, 14, 16, 

The difference between your two versions is

1,10c1,10
< branch: #  0; line:     2-    5; sop:     0; eop:     3; out1:  14
< branch: #  4; line:     6-    6; sop:     4; eop:     5; out1:  10
< branch: #  6; line:     7-    6; sop:     6; eop:     9; out1:  10
< branch: # 10; line:     6-    6; sop:    10; eop:    11; out1:  12; out2:   6
< branch: # 12; line:     5-    5; sop:    12; eop:    13; out1:  14
< branch: # 14; line:     5-    5; sop:    14; eop:    15; out1:  16; out2:   4
< branch: # 16; line:    10-   10; sop:    16; eop:    20; out1:  -2
< path #1: 0, 14, 16, 
< path #2: 0, 14, 4, 10, 12, 14, 16, 
< path #3: 0, 14, 4, 10, 6, 10, 12, 14, 16, 
---
> branch: #  0; line:     2-    5; sop:     0; eop:     3; out1:  13
> branch: #  4; line:     6-    6; sop:     4; eop:     5; out1:   9
> branch: #  6; line:     7-    6; sop:     6; eop:     8; out1:   9
> branch: #  9; line:     6-    6; sop:     9; eop:    10; out1:  11; out2:   6
> branch: # 11; line:     5-    5; sop:    11; eop:    12; out1:  13
> branch: # 13; line:     5-    5; sop:    13; eop:    14; out1:  15; out2:   4
> branch: # 15; line:    10-   10; sop:    15; eop:    19; out1:  -2
> path #1: 0, 13, 15, 
> path #2: 0, 13, 4, 9, 11, 13, 15, 
> path #3: 0, 13, 4, 9, 6, 9, 11, 13, 15, 

It ($a += $b) just uses a different, slightly slower(!) path. Yes, slower: my tests gave 17s. for $a = $a + $b and 20s. for $a += $b. Not much, although significant. And also not that much of a difference to JavaScript.

Two minutes for your version of PHP is quite large, even an old PHP-5 did it in 40 seconds here. I couldn't find anything in the ChangLogs but you might try an update if possible. Or try different optimizations if you compiled it yourself because a MAC is still different from a "normal" PC.

deamentiaemundi
  • 5,502
  • 2
  • 12
  • 20