用最簡單易懂的道理告訴你,爲何JavaScript在現代引擎(V8,JavaScriptCore)下,能表現出卓越性能!

簡單性能測試

首先,咱們先來作一個簡單的性能測試,對比一下Java,JavaScript,PHP,Ruby這四門語言。這個性能測試,是計算斐波那契數列(兔子數列)。好比計算n=5的兔子數列,結果是:1,1,2,3,5,8,13,21,34,55(1+1=2...21+34=35)。php

這很容易經過一個遞歸來實現,JavaScript代碼以下:java

function fs(n) {
        if (n <= 2) {
            return 1;
        } else {
            return fs(n - 1) + fs(n - 2);
        }
    }

能夠看出,這個測試主要偏重CPU棧操做。node

以上面這個函數爲基礎,加上一些邏輯,分別使用Java,JavaScript,PHP,Ruby這四門語言編寫了腳本,計算n=40的兔子數列,咱們看一下結果吧。(代碼在本文最後,^_^)算法

  • 首先是Java,編譯出字節碼耗時約1s,運行字節碼耗時約1s,666。編程

  • 其次是JavaScript,在node環境下運行耗時約3.5s,在瀏覽器環境(Safari)下約8s,66。segmentfault

  • 接着是Ruby,出人意料的結果,約39s,6不起來了。瀏覽器

  • 最後是PHP,約80s,233。ruby

  • C或者C++的代碼我沒有寫,確定跑得比狗還快。函數

這個簡單性能測試並不能說明語言優劣,只是比較好玩而已,代碼在本文最後,有興趣能夠去運行一下。性能

Java鶴立雞羣的緣由

靜態類型vs動態類型

靜態類型語言指的是編譯的時候就可以知道每一個變量的類型,咱們編程的時候固然也須要給定類型,如Java中的整型int,浮點型float等。

動態類型語言指的是運行的時候纔可以知道每一個變量的類型,編程的時候也無需顯示指定類型,如JavaScript中的var,PHP中的$。

看上去,靜態類型仍是動態類型對性能沒什麼影響,實際上卻影響很大。

歸納來講就是,靜態類型語言在編譯後會大量利用類型已知的優點,好比int類型,佔用4個字節,編譯後的代碼就可使用內存地址加偏移量的方法存取變量。而地址+偏移量的算法彙編很是容易實現。

那動態類型語言是如何作的呢?歸納的來講就是當作字符串統統存下來,以後存取就用字符串匹配。

能夠感覺到這兒存在的性能差別了嗎?

編譯型vs解釋性

編譯型語言,就像C/C++,代碼要通過編譯器編譯成可執行程序後才能夠運行。這個編譯過程沒什麼時間要求,因此編譯器能夠作大量代碼優化措施,有時候編譯要很久很久。

解釋型語言,就像JavaScript,就是引擎直接讀源碼,而後就出結果,固然這樣子作效率很是低。就像靠人腦去讀源碼,而後寫答案同樣。

奇葩型語言,就像Java,有編譯過程,但編譯產出的是中間代碼(字節碼),這個過程也有充分的時間作優化。也有解釋過程,字節碼須要由Java虛擬機解釋執行。

從這兒,大概能夠理解,爲何C/C++運行效率比Java更高。由於無論怎麼說,直接運行二進制碼都比解釋執行字節碼來得快吧。

因此,有趣的事情就來了,C/C++是大哥,Java是二哥,一羣解釋型腳本語言是小弟們。大哥,獨孤求敗。二哥,想法子和大哥站在一條線上。小弟們,盡全力跟上二哥。

現代JavaScript引擎的努力

先來看看,Java虛擬機作了哪些努力?

Java想的確定是優化虛擬機解釋執行字節碼的速度,這兒正是和大哥拉開差距的地方。從大哥那學了不少招。其中重要的一招就是JIT(Just-In-Time),主要的思想就是解釋器在解釋字節碼的時候,會將部分字節碼轉化成本地代碼(彙編代碼),這樣能夠被CPU直接執行,而不是解釋執行,從而極大地提升性能。

重點來看看,JavaScript引擎作了哪些努力?
JavaScript從前輩那裏學習了不少,總結來講有:

  • 優化數據表示,彌補動態類型的性能缺陷

  • 引入一個編譯過程,而不是直接解釋執行,但這個編譯過程和運行是一塊兒的,時間的權衡變得很是重要。

  • JIT技術,與Java中的JIT原理相同

V8引擎與JavaScriptCore引擎

各個JavaScript優化的具體實現不太同樣。

舉例子來講,V8引擎對於編譯和JIT的作法是,在編譯階段的過程是:源碼=》抽象語法樹=》本地代碼。其中從抽象語法樹到本地代碼的過程使用的是JIT全碼生成器,其做用是將抽象語法樹轉換成各個硬件平臺和直接運行的本地代碼。V8引擎的這種思路看起來像想要越過二哥Java,直接學大哥C/C++啊。

而JavaScriptCore引擎的作法是更接近二哥的,在編譯階段的過程是:源碼=》抽象語法樹=》字節碼(中間代碼)。對這個階段像極了二哥Java的編譯過程,只是這裏小弟可沒有充裕的時間作優化。因而大量的字節碼優化措施被延後,好比JIT。JavaScriptCore引擎使用DFG JIT、LLVM等繼續對字節碼作優化。

權衡時間很重要,一個很好的優化措施但耗時太多,引入以後反而讓JavaScript總體的運行時間變長了,得不償失。另外,還有許多人提出,要不要徹底抄二哥的,就是也引入一個提早編譯的過程,233

Ruby、PHP爲何在前面的測試中落敗

具體緣由可能仍是在引擎吧,可能它們的引擎遠沒有像V8這麼努力。

總結

首先,對於底層的理解,有助於編寫上層的代碼。好比如今咱們去理解JavaScript代碼的時候,會更深入。具體能夠看這篇文章試試,《經過這一段代碼,讓咱們從新認識JavaScript》
其次,多一些話題吧,好比之後和同伴談起V8引擎(裝B)的時候,說我這個例子還不錯吧。

性能測試代碼,Java、JavaScript、Ruby、PHP計算斐波那契數列(兔子數列)

Java

import java.util.Date;
public class Fbnq {
    public static void main(String []args) {
        int num = 40;
        long startTime = new Date().getTime();
        //System.out.println(startTime);
        String result = fslog(num);
        long endTime = new Date().getTime();
        //System.out.println(endTime);
        float needTme = (endTime - startTime)/1000;
        
        System.out.println("time:"+needTme+"s,result:"+result);
    }
    public static int fs (int n){
        if(n <= 2){
            return 1;
        }else{
            return fs(n-1)+fs(n-2);
        }
    }
    public static String fslog(int num){
        String rsString = "";
        for(int i=1;i<=num;i++){
            int rs = fs(i);
            System.out.println(rs);
            if(i == 1){
                rsString = rsString + rs;
            }else{
                rsString = rsString + "," + rs;
            }
        }
        return rsString;
    }
}

JavaScript

var num = 40;
var startDate = new Date().getTime();
var result = logfs(num);
var endDate = new Date().getTime();

console.log("time:" + ((endDate - startDate) / 1000) + "s", "result:" + result);

function logfs(num) {
    var rsString = "";
    for (var i = 1; i <= num; i++) {
        var rs = fs(i);
        if (i === 1) {
            rsString = rsString + rs;
        } else {
            rsString = rsString + "," + rs;
        }
        console.log(rs);
    }
    return rsString;
    function fs(n) {
        if (n <= 2) {
            return 1;
        } else {
            return fs(n - 1) + fs(n - 2);
        }
    }
}

Ruby

def fs (n)
    if n < 2
        return 1;
    else
        return (fs (n-1)) + (fs (n-2));
    end
end

def fslog (num)
    num = num - 1;
    rsString = "";
    for i in 1..num
        rs = fs i;
        puts rs;
        if i === 1
            rsString = rsString + "#{rs}";
        else
            rsString = rsString + ",#{rs}";
        end
        
    end
    return rsString;
end

num = 40;
startTime = Time.now.to_f*1000;
rsString = fslog num;
endTime = Time.now.to_f*1000;

needTime = (endTime - startTime)/1000;

puts "time:#{needTime}s,result:#{rsString}";

Php

<?php
    $num = 40;
    $startTime = microtime(true)*1000;
    var_dump($startTime);
    consoleLog($startTime);
    $result = fslog($num);
    $endTime = microtime(true)*1000;



    consoleLog("time:".(($endTime - $startTime)/1000)."s,result:".$result);

    function fs($n){
        if ($n <= 2) {
            return 1;
        } else {
            return fs($n - 1) + fs($n - 2);
        }
    }
    function fslog($num){
        $rsString = "";
        for($i=1;$i<=$num;$i++){
            $rs = fs($i);
            if($i===1){
                $rsString = $rsString."".$rs;
            }else{
                $rsString = $rsString.",".$rs;
            }
            consoleLog($rs);
        }
        return $rsString;
    }
    function consoleLog($str){
        echo $str."\n";
    }
?>

參考

  • 《你所不知道的JavaScript(上卷)》

  • 《WebKit技術內幕》

  • 《深刻淺出Node.js》

相關文章
相關標籤/搜索