开源中国

我们不支持 IE 10 及以下版本浏览器

It appears you’re using an unsupported browser

为了获得更好的浏览体验,我们强烈建议您使用较新版本的 Chrome、 Firefox、 Safari 等,或者升级到最新版本的IE浏览器。 如果您使用的是 IE 11 或以上版本,请关闭“兼容性视图”。
Asm.js: Javascript的编译目标 - 技术翻译 - 开源中国社区

Asm.js: Javascript的编译目标 【已翻译100%】

自由之信 推荐于 4年前 (共 19 段, 翻译完成于 01-18) 评论 27
收藏  
65
推荐标签: asm.js 待读

正如许多开发者一样,我也为Asm.js的前景而感到兴奋不已。最近的新闻——Asm.js正在被Firefox支持——引起了我的兴趣。同样感兴趣的还有Mozilla和Epic声明(mirror)他们已经为Asm.js而支持Unreal Engine 3——并且运行十分良好。

获得一个C++游戏引擎运行Javascript,并使用WebGL来渲染,这是一个重大的突破,这个突破很大程度上归功于Mozilla开发的工具链,才使得这一切变得可能。

由于Unreal Engine 3开始支持Asm.js,我浏览了来自Twitter,blogs和其他地方的回应,当一部分开发者表现出对这项制造奇迹的技术跃跃欲试时,我也看到许多疑惑:Asm.js是一个插件吗?Asm.js能让我平时使用的Javascript运行的更快吗?它兼容所有浏览器吗?从这些回应中,我认为Asm.js及其相关技术很重要,我将解释这些技术,好让开发者明白发生了什么以及他们将会如何从这些技术中获益。另外,为了写出这篇我对这项技术的概览,我也请教了David Herman(Mozilla研究院的高级研究员)一大堆关于Asm.js和如何梳理这些知识的问题。

tnjin
 翻译得不错哦!

什么是 Asm.js?

为了理解 Asm.js 及其适用与浏览器的所在,你需要知道它的由来以及它存在的意义。
Asm.js 来自于 JavaScript 应用的一个新领域: 编译成JavaScript的C/C++应用. 它是 JavaScript 应用的一个全新流派,由 Mozilla 的 Emscripten项目催生而来。
Emscripten 将 C/C++ 代码传入  LLVM, 并将 LLVM生成的字节码转换成 JavaScript (具体的, Asm.js, 是 JavaScript 的一个子集).

如果被编译成的 Asm.js 的代码做了一些渲染工作,那么它几乎总是由WebGL来处理的 (并且由 OpenGL 来渲染). 这样技术上就利用了JavaScript 和浏览器的好处,但几乎完全避开了页面中Javascript使用的实际的、常规的代码执行和渲染路径.

LeoXu
 翻译得不错哦!

Asm.js是Javascript的一个子集,它深度的限制了其所能做的范围和所能操作的对象。这样做才能使得Asm.js的代码能够尽可能运行的快,从而尽可能减少各种假定情况的出现,从而能够把Asmjs代码直接转变成为汇编代码。有一个特别值得注意的是 - Asmjs还只是Javascript - 不需要浏览器的插件或者是别的特性去运行它(虽然一个能够检测出并且优化Asmjs代码的浏览器当然是要快一些)。它是Javascript的一个特定的子集,为性能优化而生,特别是为那些需要编译成为Javascript的应用程序来做优化。

最佳的理解Asmjs工作的方式,就是看看一些Asmjs-编译化的代码。让我们看看这个函数,这是从真实的Asmjs编译模块里面提取出来的函数(来自BananaBread demo)。我对代码格式做了调整,所以看起来更加合乎代码片段的阅读,原本它是一个高度压缩的JavaScript代码中的一个很大的计算机二进制对象。

function Vb(d) {
d = d | 0;
var e = 0, f = 0, h = 0, j = 0, k = 0, l = 0, m = 0, n = 0,
o = 0, p = 0, q = 0, r = 0, s = 0;
e = i;
i = i + 12 | 0;
f = e | 0;
h = d + 12 | 0;
j = c[h >> 2] | 0;
if ((j | 0) > 0) {
c[h >> 2] = 0;
k = 0
} else {
k = j
}
j = d + 24 | 0;
if ((c[j >> 2] | 0) > 0) {
c[j >> 2] = 0
}
l = d + 28 | 0;
c[l >> 2] = 0;
c[l + 4 >> 2] = 0;
l = (c[1384465] | 0) + 3 | 0;
do {
if (l >>> 0 < 26) {
if ((4980736 >>> (l >>> 0) & 1 | 0) == 0) {
break
}
if ((c[1356579] | 0) > 0) {
m = d + 4 | 0;
n = 0;
while (1) {
o = c[(c[1356577] | 0) + (n << 2) >> 2] | 0;
do {
if (a[o + 22 | 0] << 24 >> 24 == 24) {
if (!(Vp(d, o | 0) | 0)) {
break
}
p = (c[m >> 2] | 0) + (((c[h >> 2] | 0) - 1 | 0) * 40 & -1) + 12 | 0;
q = o + 28 | 0;
c[p >> 2] = c[q >> 2] | 0;
c[p + 4 >> 2] = c[q + 4 >> 2] | 0;
c[p + 8 >> 2] = c[q + 8 >> 2] | 0;
c[p + 12 >> 2] = c[q + 12 >> 2] | 0;
c[p + 16 >> 2] = c[q + 16 >> 2] | 0;
c[p + 20 >> 2] = c[q + 20 >> 2] | 0;
c[p + 24 >> 2] = c[q + 24 >> 2] | 0
}
} while (0);
o = n + 1 | 0;
if ((o | 0) < (c[1356579] | 0)) {
n = o
} else {
break
}
}
r = c[h >> 2] | 0
} else {
r = k
} if ((r | 0) == 0) {
i = e;
return
}
n = c[j >> 2] | 0;
if ((n | 0) >= 1) {
i = e;
return
}
m = f | 0;
o = f + 4 | 0;
q = f + 8 | 0;
p = n;
while (1) {
g[m >> 2] = 0.0;
g[o >> 2] = 0.0;
g[q >> 2] = 0.0;
Vq(d, p, f, 0, -1e3);
n = c[j >> 2] | 0;
if ((n | 0) < 1) {
p = n
} else {
break
}
}
i = e;
return
}
} while (0);
if ((c[1356579] | 0) <= 0) {
i = e;
return
}
f = d + 16 | 0;
r = 0;
while (1) {
k = c[(c[1356577] | 0) + (r << 2) >> 2] | 0;
do {
if (a[k + 22 | 0] << 24 >> 24 == 30) {
h = b[k + 14 >> 1] | 0;
if ((h - 1 & 65535) > 1) {
break
}
l = c[j >> 2] | 0;
p = (c[1384465] | 0) + 3 | 0;
if (p >>> 0 < 26) {
s = (2293760 >>> (p >>> 0) & 1 | 0) != 0 ? 0 : -1e3
} else {
s = -1e3
} if (!(Vq(d, l, k | 0, h << 16 >> 16, s) | 0)) {
break
}
g[(c[f >> 2] | 0) + (l * 112 & -1) + 56 >> 2] = +(b[k + 12 >> 1] << 16 >> 16 | 0);
h = (c[f >> 2] | 0) + (l * 112 & -1) + 60 | 0;
l = k + 28 | 0;
c[h >> 2] = c[l >> 2] | 0;
c[h + 4 >> 2] = c[l + 4 >> 2] | 0;
c[h + 8 >> 2] = c[l + 8 >> 2] | 0;
c[h + 12 >> 2] = c[l + 12 >> 2] | 0;
c[h + 16 >> 2] = c[l + 16 >> 2] | 0;
c[h + 20 >> 2] = c[l + 20 >> 2] | 0;
c[h + 24 >> 2] = c[l + 24 >> 2] | 0
}
} while (0);
k = r + 1 | 0;
if ((k | 0) < (c[1356579] | 0)) {
r = k
} else {
break
}
}
i = e;
return
}
自由之信
 翻译得不错哦!

从技术上说,这是Javascript代码,但是我们已经看到这段代码一点都不像大多我们正常看到的操作DOM的Javascript。通过看这段代码,我们可以发现几件事:

  • 这段特定的代码只能处理数值。事实上这是所有Asm.js代码的情形。Asm.js只能处理被挑出的几种不同的数值类型,而没有提供其他的数据类型(包括字符串,布尔型和对象)。

  • 所有外部数据在一个称为堆的对象中存储并被引用。堆在本质上是一个大数组(应当是一个在性能上高度优化的类型化数组)。所有的数据在这个数组中存储——有效的替代了全局变量,结构体,闭包和其他形式的数据存储。

  • 当访问和赋值变量时,结果被统一的强制转换成一种特定类型。例如f = e | 0;给变量f赋值e,但它也确保了结果的类型是一个整数(|0把值转换成整数,确保了这点)。这也发生在浮点型上——注意0.0和g[...] = +(...)的使用.

  • 看看从数据结构中写入读出的值,用c表示的数据结构好像是一个Int32Array(存储32位整数,因为这些值总是通过|0被转换成或者转换自一个整数),g好像是一个Float32Array(存储32位浮点数,因为这些值总是通过包裹上+(...)转换成浮点数)。

这么做以后,结果就是高度优化,并且可以直接从Asm.js语法转换成汇编,而不必像常常要对Javascript做的那样解释它。它有效地削减了使像Javascript之类的的动态语言缓慢的东西:例如需要垃圾收集器和动态类型。

magiclogy
 翻译得不错哦!

作为一个更容易理解的Asm.js代码示例,我们来看看一个Asm.js规范上的例子:

function DiagModule(stdlib, foreign, heap) {
    "use asm";

    // Variable Declarations
    var sqrt = stdlib.Math.sqrt;

    // Function Declarations
    function square(x) {
        x = +x;
        return +(x*x);
    }

    function diag(x, y) {
        x = +x;
        y = +y;
        return +sqrt(square(x) + square(y));
    }

    return { diag: diag };
}

看看这个模块,它完全能让人理解!阅读这些代码,我们能更好的理解Asm.js模块的结构。一个模块包含在一个函数中,它以顶部的"use asm";指令开始。它提示解释器这个函数里所有的东西可以被当成Asm.js处理,并可以直接被编译成汇编代码。

注意在函数顶部的三个参数:stdlib,foreigh和heap。stdlib对象包含了很多内建数学函数的引用。foreign提供了自定义用户功能(例如在WebGL中绘制图形)的访问。最后,heap给了你一个ArrayBuffer,它可以通过很多透镜(例如Int32Array和Float32Array)来观察。

magiclogy
 翻译得不错哦!

该模块剩下的被分成了三部分:变量声明,函数声明,还有最后把函数导出暴露给用户的一个对象。

导出是尤其要去理解的一个重点,因为它既让所有模块中的代码被当成Asm.js处理,又使得代码可以被其他普通的Javascript代码使用。因此,在理论上你可以通过使用上面的DiagModule代码,写下如下代码:

document.body.onclick = function() {
    function DiagModule(stdlib){"use asm"; ... return { ... };}

    var diag = DiagModule({ Math: Math }).diag;
    alert(diag(10, 100));
};

这带来了一个Asm.js模块DiagModule,它被Javascript解释器特殊处理,但仍然能够被其他Javascript代码使用(我们仍能访问并使用它,比如一个单击事件处理程序)。

magiclogy
 翻译得不错哦!

性能如何?

现在Asm.js唯一的实现就是 nightly versions of Firefox(而且也只是针对特定的几个平台)。原来的数字告诉我们Ams.js的性能是非常非常不错的。对于复杂的应用(比如上面的游戏)性能仅仅比普通C++编译的慢两倍(可以和Java或者C#相媲美)。实际上,这已经比目前浏览器的运行时环境要快很多了,几乎是最新版的Firefox或者Chrome执行速度的4~10倍。

基于目前最好测试,可以看出Asm.js在性能上有很大的提升。考虑到现在仅仅是Asm.js的最初开发阶段,相信在不久的将来就会有更大的性能提升。

看到Asm.js和当前的Firefox和Chrome引擎的性能差距是很有意思的。一个4~10倍的性能差异是非常巨大的(就好像拿这些浏览器和IE6做性能对比一样)。有趣的是虽然有这么大的性能差异,但是许多的Asm.js演示例子仍然是可以在Chrome和Firefox——这些代表着当前Javascript先进技术的引擎——上使用的。这也就是说他们的性能明显不如一个运行着优化过的Asm.js代码的浏览器相提并论。

tnjin
 翻译得不错哦!

使用情况

需要说明的是现在几乎所有基于Asm.js的应用都是C/C++应用使用Emscripten编译的。可以肯定的说,在不久的将来,这类即将运行在Asm.js的应用,将会从可以在浏览器中运行这一可移植性中获益,但是在支持javascript方面有一定复杂度的应用将不可行。

到目前为止,大部分的使用情况下,代码性能是至关重要的:比如运行游戏,图像,处理语言翻译和库。从一个关于Emscripten项目列表的概览可以看到许多即将被广大开发者使用的技术。

  • 许多游戏引擎已经被支持。一个好的演示也许就是BananaBread FPS游戏(源代码),它可以直接在浏览器中运行,支持多玩家和机器人。

  • 支持javascript的LaTeX,即textlive.js,使用Emscripten,允许你完全在你的浏览器中编辑PDF文档。

  • 支持javascript的SQLite,能够在Node.js中运行。

  • NaCL:一个网络加密解密的库。

tnjin
 翻译得不错哦!

Asm.js支持

就像之前提到的那样,现在只有nightly版本的Firefox支持Asm.js的优化。

但是,要注意Asm.js格式的Javascript代码仍然是Javascript,虽然其存在一些限制。这样,其他的浏览器即使不支持Asm.js仍可以将其作为普通的Javascript代码运行。

有关代码性能重要而令人不解的一点是:如果浏览器不支持typed array或者不能对Asm.js代码进行特殊的编译,Asm.js的性能会变的很差。当然,这并不止针对Asm.js,如果没有这些特性,浏览器的性能在其他方面也会收到影响。

realZ
 翻译得不错哦!

Asm.js与Web开发

你可能已经看出来了,上面的Asm.js代码不是手工输入的的。Asm.js需要一些特殊的工具来编写,而且其开发和编写普通的Javascript代码有很大区别。目前一般的Asm.js应用都是从C/C++编译到Javascript的,很显然它们都不会与DOM进行任何交互,而是直接与WebGL打交道。

为了能让一般的开发者使用,需要一些更能让人接受的中间语言。目前LLJS已经逐渐实现向Asm.js编译了。需要注意的是,LLJS这样的语言同样与常规的Javascript有很大区别,会让许多Javascript开发者感到困扰。即使是用LLJS这样更加友好的语言,编写Asm.js必须要对复杂的代码进行优化,这恐怕只有资深开发者能够胜任了。

就算有了LLJS或者别的语言来帮助我们编写Asm.js,我们也没有同样性能优异的DOM可以使用。理想的环境应该是将LLJS与DOM一起编译产生单一的可执行二进制文件。我还想不出这样做性能会有多好,但是我想这么做!

realZ
 翻译得不错哦!
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们
评论(27)
Ctrl/CMD+Enter

不明觉厉
不明觉历
不明觉历
在浏览器玩dota一直有这个想法

引用来自“合理”的评论

在浏览器玩dota一直有这个想法

鼠标猛地向右上角移动,一个左键,然后就悲剧了

引用来自“yeezia”的评论

引用来自“合理”的评论

在浏览器玩dota一直有这个想法

鼠标猛地向右上角移动,一个左键,然后就悲剧了

浏览器也是可以全屏的

引用来自“yeezia”的评论

引用来自“合理”的评论

在浏览器玩dota一直有这个想法

鼠标猛地向右上角移动,一个左键,然后就悲剧了

浏览器突然因为其他页面里的flash崩溃掉(ff常见)……

引用来自“yeezia”的评论

引用来自“合理”的评论

在浏览器玩dota一直有这个想法

鼠标猛地向右上角移动,一个左键,然后就悲剧了

不会啊,提前F11就得了
不明觉厉.
不明觉历
不明觉厉
不觉明厉
不会有任何意义,谁会手动写asm
我觉得浏览器原生支持java编译后的class文件都比这有意义,最好是像js模式的,而不是applet
╮(╯▽╰)╭ 这个真的有点看不懂其意义何在了
~~就是写了一个Emscripten的javascript引擎来执行 c++通过llvm生成的字节码(不是最终代码)

引用来自“唐阳”的评论

我觉得浏览器原生支持java编译后的class文件都比这有意义,最好是像js模式的,而不是applet

主要是考虑安全性。
fireforx这样做是有想法的,就是firefox os,就是有一天在某个设备上,能运行firefox浏览器,而浏览器里因为支持asm.js而性能接近于原生应用,从而大量的应用都可以简单的用js实现~
我怎么感觉和dart很像

引用来自“NeoCD”的评论

我怎么感觉和dart很像

差很多。dart算是一门新语言了。而asm.js就还只是js。
顶部