Почему этот код V8/Javascript работает так плохо?

Я изучал некоторые интересные тестовые тесты, чтобы увидеть, насколько хорошо node.js может выполнять по сравнению с другими языками: http://benchmarksgame.alioth.debian.org/u32/compare.php?lang=node&lang2=php

Хотя результаты в основном касаются алгоритмических проблем, которые вы обычно предпочитаете решать с помощью варианта C или Fortran, один тест выделяется как невероятно плохо для V8:

pidigits - 52x медленнее, чем PHP

Так как v8 работает лучше всего по сравнению с PHP во всех других тестах, я предполагаю, что есть что-то не так с кодом или некоторыми конкретными для реализации V8/Javascript, что заставляет его работать так плохо. Что это?

Код 1: V8

// The Computer Language Benchmarks Game
//  http://shootout.alioth.debian.org
//
//  Contributed by Matthew Wilson 
//  biginteger derived from Tom Wu jsbn.js


var compareTo, multiply, divide, addTo, add, intValue, shiftLeft, nbv;

function main($n) {
  var $i=1, $s="", $d, neg10=nbv(-10), three=nbv(3), ten=nbv(10), g = 1, $g,
  digits=Array(10), $z0=nbv(1), $z1=nbv(0), $z2=nbv(1), negdigits=Array(10),
  k = 0, $k, l = 2, $l, a;

  for(var i=0; i<10; ++i) { negdigits[i] = multiply(digits[i] = nbv(i),neg10) }

  do {
    while ( compareTo($z0,$z2) > 0
         || ($d = intValue(divide(add(multiply($z0,three),$z1),$z2))) != 
             intValue(divide(add(shiftLeft($z0,2),$z1),$z2))
    ) {
      $z1 = multiply($z1,$g = nbv(g+=2));
      $z2 = multiply($z2,$g);
      addTo($z1, multiply($z0,$l = nbv(l+=4)), $z1);
      $z0 = multiply($z0,$k = nbv(++k));
    }
    $z0 = multiply($z0,ten);
    $z1 = multiply($z1,ten);
    addTo($z1, multiply($z2,negdigits[$d]), $z1);
    $s += $d;

    if ($i % 10 == 0) { print($s+"\t:"+$i); $s="" }
  } while (++$i <= $n)

  if (($i = $n % 10) != 0) { $s += Array(11-$i).join(' ') }
  if ($s.length > 0) { print($s+"\t:"+$n) }
}

var functions;
load('/home/dunham/shootout/bench/Include/javascript/biginteger.js');

compareTo=functions[0];
multiply=functions[1];
divide=functions[2];
addTo=functions[3];
add=functions[4];
nbv=functions[5];
shiftLeft=functions[6];
intValue=functions[7];

main.call(this, 1*arguments[0]*1)

Код 2: PHP

<?php /* The Great Computer Language Shootout 
   http://shootout.alioth.debian.org/
   contributed by Isaac Gouy 
   php -q pidigits.php 27
*/

class Transformation {
   var $q, $r, $s, $t, $k;

   function Transformation($q, $r, $s, $t){
      $this->q = $q;
      $this->r = $r;      
      $this->s = $s;
      $this->t = $t;               
   }

   function Unity(){
      return new Transformation("1", "0", "0", "1");              
   }   

   function Zero(){
      return new Transformation("0", "0", "0", "0");              
   }      

   function Compose($a){
      $qq = bcmul($this->q, $a->q);
      $qrrt = bcadd(bcmul($this->q, $a->r), bcmul($this->r, $a->t));
      $sqts = bcadd(bcmul($this->s, $a->q), bcmul($this->t, $a->s));
      $srtt = bcadd(bcmul($this->s, $a->r), bcmul($this->t, $a->t));   
      return new Transformation($qq, $qrrt, $sqts, $srtt);
   }

   function Extract($j){
      $bigj = strval($j);
      $qjr = bcadd(bcmul($this->q, $bigj), $this->r);
      $sjt = bcadd(bcmul($this->s, $bigj), $this->t);
      $d = bcdiv($qjr, $sjt);
      return floor($d);
   }

   function Next(){ 
      $this->k = $this->k + 1;
      $this->q = strval($this->k);
      $this->r = strval(4*$this->k + 2);
      $this->s = "0";
      $this->t = strval(2*$this->k + 1);
      return $this;      
   }                
}

class PiDigitStream {
   var $z, $x, $inverse;

   function PiDigitStream(){
      $this->z = Transformation::Unity();
      $this->x = Transformation::Zero();      
      $this->inverse = Transformation::Zero();   
   }

   function Produce($j){
      $i = $this->inverse;
      $i->q = "10";
      $i->r = strval(-10*$j);
      $i->s = "0";
      $i->t = "1";
      return $i->Compose($this->z);
   }   

   function Consume($a){
      return $this->z ->Compose($a);  
   }

   function Digit(){
      return $this->z ->Extract(3);  
   }  

   function IsSafe($j){
      return $j == ($this->z ->Extract(4));  
   }    

   function Next(){
      $y = $this->Digit();
      if ($this->IsSafe($y)){
         $this->z = $this->Produce($y);
         return $y;
      } else {
         $this->z = $this->Consume($this->x ->Next());
         return $this->Next();      
      }
   } 
}


$n = $argv[1];
$i = 0;
$length = 10;
$pidigit = new PiDigitStream;

while ($n > 0){
   if ($n < $length){
      for ($j=0; $j<$n; $j++) printf("%d",$pidigit->Next());
      for ($j=$n; $j<$length; $j++)  print " ";
      $i += $n;
   } else {
      for ($j=0; $j<$length; $j++) printf("%d",$pidigit->Next());
      $i += $length;   
   }
   print "\t:$i\n";
   $n -= $length;
}
?>

Ответ 1

PHP использует Библиотека математики BC высоко оптимизирован GMP library для своих вычислений, который написан на C (и сборке в некоторых местах), где версия V8 использует большой целочисленный класс, написанный на JavaScript (он говорит "на основе" Tom Wu jsbn.js). Вероятно, более точно сказать, что эталон сравнивает V8 и C большую целую производительность, чем V8 и PHP.

PHP-код в вопросе - это другая версия записи PHP, которая использует библиотеку BC Math, и на самом деле медленнее, чем V8 (спасибо igouy). Библиотека BC также написана на C, но она работает с базовыми номерами 10 (это оболочка PHP для библиотеки, используемой версиями GNU dc и bc), и не так сильно оптимизирована, как GMP.

Ответ 2

Из любопытства я написал альтернативную версию, используя node-bigint (который обертывает libgmp). Я сравнил самую быструю реализацию C с моей версией теста. Производительность так же, как и следовало ожидать, в сравнении с другими языковыми реализациями с использованием libgmp.

Результаты

C (скомпилирован с gcc -pipe -Wall -O3 -fomit-frame-pointer pidigits.c -o pidigits -lgmp)

./pidigits-c 10000  1.11s user 0.00s system 99% cpu 1.116 total

node (0.6.18)

node pidigits-gmp.js 10000  3.61s user 3.15s system 100% cpu 6.712 total

Источник

var bigint = require('bigint');

function calculatePi(N) {
  var i = 0,
      k = 0,
      k1 = 1,
      ns = 0,

      a = bigint(0),
      d = bigint(1),
      m = bigint(0),
      n = bigint(1),
      t = bigint(0),
      u = bigint(0);

  while (1) {
    k += 1;
    k1 += 2;
    t = n.shiftLeft(1);
    n = n.mul(k);
    a = a.add(t).mul(k1);
    d = d.mul(k1);

    if (a.cmp(n) >= 0) {
      m = n.mul(3).add(a);
      t = m.div(d);
      u = m.mod(d).add(n);

      if (d.cmp(u) > 0) {
        ns = ns * 10 + t.toNumber();
        i += 1;

        if (i % 10 === 0) {
          console.log(ns + '\t:' + i);
          ns = 0;
        }

        if (i >= N) break;

        a = a.sub(d.mul(t)).mul(10);
        n = n.mul(10);
      }
    }
  }
}

calculatePi(process.argv[2] || 10);