zoukankan      html  css  js  c++  java
  • 转:HTML5标准与性能之四:asm.js

    HTML5标准与性能之四:asm.js

    之前的几篇文章分别介绍了WebWorkersTyped ArrayParallelArray,最后,我们再来介绍一下与性能相关的标准:asm.js。

    asm.js

    asm.js是由Mozilla提出的一个基于JS的语法标准,主要是为了解决JS引擎的执行效率问题,尤其是使用Emscripten从C/C++语言编译成JS的程序的效率,目前只有Mozilla的Firefox Nightly中支持。

    Emscripten

    Emscripten是Mozilla的一个实验性项目,目的是把C/C++开发的应用编译成JS或HTML5的应用,编译过程中需要首先把C/C++程序编译成LLVM的中间代码,然后再转换成JS代码,这样做的主要原因是可以很好地复用现有的针对LLVM的优化。

    C/C++是一种强类型的语言,这很好地对应了不同字长的CPU指令,每一种数据类型都有固定的上下限,一旦计算超出这个上下限就会产生溢出,比如char类型的取值范围是[-128,127],而(char)(127 + 1)这个计算就会溢出,得到(char)-128

    JS语言不仅是弱类型的,而且数值类型只有一种-NumberNumber类型的数据采用双精度64位格式的IEEE 754值表示。如果用JS模拟C/C++类型的数值计算,就要模拟各种类型数据计算时的溢出效果。我们通过下面这个简单的C程序来看看如何用JS来模拟这样的计算。

    1 char xInt8 = 127;
    2 char yInt8 = xInt8 + 1; // 溢出:yInt8 == (char) -128
    3 cahr zInt8 = xInt8 / 2; // 舍入:zInt8 == (char) 63

    上面这段代码通过JS模拟后的代码如下:

    1 var xInt8 = 127; // (1)
    2 var $add = (xInt8 + 1) | 0; // (2)
    3 var yInt8 = ($add << 24) >> 24; // (3)
    4 var $div = ((yInt8 | 0) / 2) & -1; // (4)
    5 var zInt8 = ($div << 24) >> 24; // (5)
    1. 8位整型变量127,相当于char xInt8 = 127;
    2. X|0让计算结果成为32位整数,此时$add == 128
    3. 先左移24位再右移24位,让第8位成为32位整数的符号位,来模拟8位整数计算,此时yInt8 == -128
    4. JS中127/2结果是浮点数63.5,利用X&-1将结果转化成32位整数63(-1的补码表示就是0xFFFFFFFF)
    5. 用上面(3)相同的方法,把结果转换成8位整数

    大家看到,利用一些位移和逻辑运算可以模拟C/C++语言中的数据计算,Emscripten就利用这个方法将C代码转换成JS代码。大家可能还记得前篇文章介绍过的Typed Array,对Typed Array元素赋值则会自动进行相应的溢出和舍入处理,因此,利用Typed Array还可以改写成以下的代码:

    1 var HEAP8 = new Int8Array(STACK_SIZE); // 构造一个Int8Array数组
    2 HEAP8[0] = 127; // char xInt8 = 127
    3 HEAP8[1] = HEAP[0] + 1; // char yInt8 = xInt8 + 1,此时yInt8是-128
    4 HEAP8[2] = HEAP[1] / 2; // char zInt8 = xInt8 / 2 

    在EmScripten中,Typed Array用来模拟C/C++中的堆栈以及指针的访问。

    语法

    asm.js不是一种新的语言,而是JS语法的一个子集,也就是说所有用asm.js写的程序都是合法的JS程序,asm.js与JS语言的关系有点类似C与C++的关系。因此,不支持asm.js的浏览器或JS引擎也可以无误地执行asm.js的代码。

    asm.js顾名思义是作为JS的汇编语言来设计的,它的语法手写起来非常困难,且难以阅读。首先,asm.js的语法利用了一些标注让JS的变量成为强类型的,这些标注与Emscripten生成的代码如出一辙,实际上asm.js的产生就是为了提高Emscripten转换后的代码执行效率的。我们来看一个例子:

    1 intValue = f1() | 0; // 利用或运算(|)标记函数f1返回值为int32整数
    2 floatValue = +f2(); // 用加号(+)标记函数f2返回值为双精度型浮点 

    同时,asm.js还规定了一个特殊的语法格式,下面这段代码是一个最简单的asm.js示例(代码来自:http://asmjs.org/spec/latest/):

    1 function MyAsmModule(stdlib, foreign, heap) {
    2 "use asm"; // "use asm"来告诉JS引擎这个函数采用asm.js编译器解析执行
    3 // module body...
    4 return { // 返回向外暴露的函数接口
    5  export1: f1,
    6  export2: f2,
    7  // ... };
    8 }
    9 var result = MyAsmModule({}, {}, null).export1(); // 调用函数export1

    在这个例子中,参数stdlib、foreign、heap由外部传入,表示: 1. stdlib:有限的标准库函数,主要是一些数学函数,对应Math对象上的方法 2. foreign:foreign function interface (FFI)外部JS函数访问接口 3. heap:传入一个ArrayBuffer对象,作为asm.js的堆

    编译和运行

    由于asm.js相当于支持了强类型,因此可以直接对应编译成机器指令执行。asm.js的代码采用另外一套AOT(Ahead Of Time)编译器,将asm.js代码预先编译成机器指令,在编译过程或运行过程中,一旦发现语法错误或违反类型标记的情况出现,便重新将代码交予JS引 擎解析执行(见下图)。

    Linking图片来自:http://asmjs.org/spec/latest/

    性能

    JS一直以来被人诟病的一个方面就是它的性能,得益于这些年来浏览器之间的竞争,让JS的性能大大提升,Google的V8、Mozilla的 SpiderMonkey以及微软的Chakra在性能方面都已经相当不错,而asm.js进一步提升到相对本地代码2倍慢的性能(如下图)。这些测试用 例使用Emscripten转换而来,Emscripten已经可以直接生成asm.js代码。 Performance数据来自:http://ejohn.org/blog/asmjs-javascript-compile-target/

    Demo

    参考

    1. asm.js specification: http://asmjs.org/spec/latest/
    2. IEEE 754: https://en.wikipedia.org/wiki/IEEE_floating_point
  • 相关阅读:
    关于烂代码的那些事(中)
    关于烂代码的那些事(上)
    关于烂代码的那些事(上)
    Maven学习总结(14)——Maven 多模块项目如何分工?
    Maven学习总结(14)——Maven 多模块项目如何分工?
    优秀Java程序员必备10招
    优秀Java程序员必备10招
    SSO单点登录学习总结(3)—— 基于CAS实现单点登录实例
    SSO单点登录学习总结(3)—— 基于CAS实现单点登录实例
    SSO单点登录学习总结(2)——基于Cookie+fliter单点登录实例
  • 原文地址:https://www.cnblogs.com/gaozehua/p/3434082.html
Copyright © 2011-2022 走看看