这是一个可深可浅的话题,会先简单介绍一下什么是asm.js,看看它长什么样子,再来聊聊asm.js为什么能带来高性能,会有一些简单的对比,然后再从工程的角度讲讲兼容性,如何打包使用等等。水平有限,肯定讲不透,就当是抛装引玉吧。
What’s asm.js
还记得前段时间的“围棋杀手”AlphaGo吧,设想一下,如果两台AlphaGo相遇,会擦出些什么火花?同样的算法,在同样的机器上跑,会是什么样的结果?两只“阿尔法狗”我是抓不到了,倒是可以看看微软写的两个AI棋手对战DEMO:Asm.js Chess Battle。
游戏说明:
On this page, we match two chess engines against one another. They are identical in nearly every detail, including source code! The only difference is that one has a flag set to be interpreted as asm.js, a specialized and strict JavaScript subset that allows JavaScript engines to employ specialized compilation to dramatically speed up execution.
Each turn is limited to 200ms – because the asm.js-optimized engine has a significant performance advantage, it can evaluate more moves per turn and has a substantially higher likelihood of victory. You can adjust the turn length and other variables in the demo to see how they affect the outcome of the game.在这个页面中,我们设置两个象棋引擎进行对战。 这两个引擎在各个细节上几乎一样,包括源代码! 唯一的区别是,其中一个引擎设置了标志,让浏览器按照asm.js规范(一个特定、严格、允许JavaScript引擎使用专门的编译来显着加快执行的JavaScript子集)来解释。
每回合“思考”时间为0.2s - 因为源码被浏览器解析为asm.js的象棋引擎具有显着的性能优势,所以它在每回合可以进行更多评估,因此具有更高的获胜可能性。
正所谓天下武功,唯快不破。运行得更快,在每一回合评估的可能越多,越容易取胜。
值得注意的是:仅在对asm.js规范做了优化的浏览器上运行才能看到上面的结果,如果你是Safari,那么抱歉,遵循asm.js规范的代码说不定会更慢。关于兼容性下面马上会提到。
主角登场
由上面那个象棋对战的演示可以看到,即使在web上,我们仍有许多场景(例如web游戏等)对运行性能有着极高的要求。对于这种极高的要求,这里介绍一种解法:asm.js
asm.js不是一门新的语言,而是JavaScript的一个子集。由Mozilla于2013年提出,主要为了提升JS引擎执行代码的速度。通俗来说,同样是js代码,符合asm.js规范的代码对JS引擎更加友好,JS引擎在解释执行这些代码更加省心(例如不用进行变量类型推断了),也就更加快。
an extraordinarily optimizable, low-level subset of JavaScript
一个极度可优化的,面向底层的JavaScript子集。
如果上面的描述你还看得一头雾水的话,说不定你在下面官方的常见问题中能找到一些答案。
如果你想深入了解asm.js的规范或者说草案标准,推荐看官方草案。
asm.js、js、C++
下面我们来看一下同一个算法(参考网上),三种不同的“语言”(其实只有C++和js)实现。
① C++
// For x = 1 to infinity:
// if x not divisible by any member of an initially empty list of primes,
// add x to the list until we have 25,000
//
// x从1递增到无穷,如果x不能整除list里的所有数,
// 那么将x添加到list,直到list中有25,000个数
#include <stdio.h>
class Primes {
public:
int getPrimeCount() const { return prime_count; }
int getPrime(int i) const { return primes[i]; }
void addPrime(int i) { primes[prime_count++] = i; }
bool isDivisibe(int i, int by) { return (i % by) == 0; }
bool isPrimeDivisible(int candidate) {
for (int i = 1; i < prime_count; ++i) {
if (isDivisibe(candidate, primes[i])) return true;
}
return false;
}
private:
volatile int prime_count;
volatile int primes[25000];
};
int main() {
Primes p;
int c = 1;
while (p.getPrimeCount() < 25000) {
if (!p.isPrimeDivisible(c)) {
p.addPrime(c);
}
c++;
}
printf("%d\n", p.getPrime(p.getPrimeCount()-1));
}
② js
// For x = 1 to infinity:
// if x not divisible by any member of an initially empty list of primes,
// add x to the list until we have 25,000
//
// x从1递增到无穷,如果x不能整除list里的所有数,
// 那么将x添加到list,直到list中有25,000个数
// 开始打点
// console.log("start");
var startTime = new Date();
function Primes() {
this.prime_count = 0;
this.primes = new Array(25000);
this.getPrimeCount = function() {
return this.prime_count; }
this.getPrime = function(i) {
return this.primes[i]; }
this.addPrime = function(i) {
this.primes[this.prime_count++] = i;
}
this.isPrimeDivisible &