用最简单易懂的道理告诉你,为什么JavaScript在现代引擎(V8,JavaScriptCore)下,能表现出卓越性能!

发布时间:2019-08-06 发布网站:脚本宝典
脚本宝典收集整理的这篇文章主要介绍了用最简单易懂的道理告诉你,为什么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)。

这很容易通过一个递归来实现,JavaScript代码如下:

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

可以看出,这个测试主要偏重CPU栈操作。

以上面这个函数为基础,加上一些逻辑,分别使用Java,JavaScript,PHP,Ruby这四门语言编写了脚本,计算n=40的兔子数列,我们看一下结果吧。(代码在本文最后,^_^)

  • 首先是Java,编译出字节码耗时约1s,运行字节码耗时约1s,666。

  • 其次是JavaScript,在node环境下运行耗时约3.5s,在浏览器环境(Safari)下约8s,66。

  • 接着是Ruby,出人意料的结果,约39s,6不起来了。

  • 最后是PHP,约80s,233。

  • C或者C++的代码我没有写,肯定跑得比狗还快。

这个简单性能测试并不能说明语言优劣,只是比较好玩而已,代码在本文最后,有兴趣可以去运行一下。

Java鹤立鸡群的原因

静态类型vs动态类型@H_406_74@

静态类型语言指的是编译的时候就能够知道每个变量的类型,我们编程的时候当然也需要给定类型,如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》

脚本宝典总结

以上是脚本宝典为你收集整理的用最简单易懂的道理告诉你,为什么JavaScript在现代引擎(V8,JavaScriptCore)下,能表现出卓越性能!全部内容,希望文章能够帮你解决用最简单易懂的道理告诉你,为什么JavaScript在现代引擎(V8,JavaScriptCore)下,能表现出卓越性能!所遇到的问题。

如果觉得脚本宝典网站内容还不错,欢迎将脚本宝典推荐好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。