为什么这个V8 / Javascript代码执行得如此糟糕?

我一直在寻找一些有趣的编程基准来看看node.js与其他语言相比的performance如何: http ://benchmarksgame.alioth.debian.org/u32/compare.php?lang=node&lang2=php

虽然结果主要是解决algorithm问题,而您通常更喜欢用C或Fortran的变体来解决这个问题,但是对于V8来说,一个testing非常不好:

pidigits – 比PHP慢52倍

因为v8在所有其他testing中的performance都比PHP更好,所以我认为在代码方面有些问题,或者是某些特定于V8 / Javascript的实现,使得它的performance非常糟糕。 它是什么?

代码1:V8

// The Computer Language Benchmarks Game // http://shootout.alioth.debian.org // // Contributed by Matthew Wilson // biginteger derived from Tom Wu's 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; } ?> 

PHP使用BC Math库高度优化的GMP库进行计算,它是用C编写的(在某些地方汇编),V8版本使用JavaScript编写的一个大整数类(它表示“基于” Tom Wu的jsbn .js )。 比起V8和PHP,V8和C大整数性能的比较可能更准确些。

问题中的PHP代码是使用BC Math库的PHP条目的不同版本,实际上比V8慢(谢谢igouy)。 BC库也是用C语言编写的,但是它可以和10个数字一起工作(它是GNU版本的dcbc所使用的库的PHP包装器),并不像GMP那样严格优化。

出于好奇,我用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 

节点(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);