关于使用asm.js解决一个浏览器插件障碍的想法
asm.js的问世似乎使这些考虑都过时了。那种C++库可以使用Emscripten编译到asm.js中。这样同样的代码就能在任何平台上运行,而且在Firefox和Chrome上都能运行的很快。而它最大的好处就是:它在老版本的Firefox中仍然可以运行,只不过不会同新的版本一般的快。理论上到目前为止,是时候来深入探讨其复杂的细节了。
尝试一下Emscripten
咋一看,Emscripten是一个Python脚本的集合,可以简单的通过从资源库检出而被“安装”。不走运的是,依赖清单上面列出了其它一些需要安装的包:LLVM, Clang和node.js。在Linux上安装这些东西相当简单,但在我的Linux发行版上可用的版本太低了。当然对我来说这并不是一个大问题,但强制用这些依赖包构建Adblock Plus有没人会告诉你的特殊的方式。
运行 gemcc -O2 -s ASM_JS=1 test.cpp 会生成相当大的文件,即使是一些琐碎的代码页至少也得60kb。 瞄一眼就明白其中大部分都是模式化的,包括了许多用来适应不同环境的代码: node.js,web页面,网络工作者,shell。目标环境(shell可能最适合用来扩展代码了)难道就不能在编译时指定么?我找不到任何办法。
一旦你接触到要使用用C++提供的功能,代码的尺寸就会像小气球一样往上窜。特别是当你用std::string做任何事情的时候,都会生成超过 500kb的代码。这几乎就意味着标准库对于大多数扩展而言存在限制,否则扩展包很容易就会变得太大。不幸的是,这会让整个解决方案明显不那么方便。
接下来我必须想想办法让其它的扩展代码同asm.js的代码能够互相通信。Emscripten 文档列出了两种选择:不那么方便一点的通过Method.cwrap()方式和更加方便一点的tembind方式。前者不允许将类映射到 Javascript中,而后者不幸又需要你在代码体积上付出沉重的代价(即使没有功能输出也需要100kb)。 所以这就是所谓的API坦途——除非你有好的理由来选择更加的方便的方式。
对我而言重要的问题是:你如何能够将一个字符串传入到已经编辑的代码中? 而奇葩的是C++中字符串参数被有点反直观的映射成了tochar*而不是 wchar_t*。一些实验中有这样的解释:所有的字符串都会转换成UTF-8编码。这意味着从Javascript中传入的字符串长度如果没有首先用 UTF-8解码,在C++中就不可用(它会用Unicode字符引用,而不是使用UTF-8编码的字节),这使得在没有做过有条件转换的前提下使用字符串成了一件相当复杂的事情。
但是其优点是不是足以弥补这两者双向转换的缺陷了呢——转换成UTF-8还有转换回来?我实现了一个简单的滚动哈希函数,一次用的是C++(用UTF-8 编码)还有一次用的是Javascript。但当我在目前的Firefox Nightly中测试者两者的时候,常规版本的Javascript大概要快上10%左右。当然,这也许就表明了我的UTF-8解码器不够高效吧。然而我使用的解码算法绝对不算是最慢的。而在Adblock Plus中为其带来性能瓶颈的地方就在对字符串的操作上,在转换上耗费了太多性能可能就意味着(利用C++)增加的性能不再那么明显了。看一看原生的asm.js
Emscripten目前似乎并不怎么满足我的需求: 它主要是为了移植大量遗留的代码库,而不是加速相对只占一小部分的Javascript代码。但如果手工去写asm.js的代码又会怎么样呢?毕竟,它同常规的Javascript代码并没有多大不同,你几乎只需要遵循很少的一些规则,如果你不这么做,验证器也会告诉你这么做。对于代码较少的部分这还是可以接受的。
深入文档,这里最大的挑战似乎就是处理堆的工作了。asm.js中的变量只能够使用原生的整型和浮点型指针 。比这些更加高级的(数组,结构体类型,字符串)就需要被分配到定义了类型的数组中代表堆。而“分配”则意味着手工的内存管理,你必须且不得不重新实现 malloc()函数。
还有其它的难题:堆的大小是固定的。asm.js代码只能处理一个缓冲区,而且不能改变其大小(当然,那也会影响Emscripten生成的代码)。问题是:我也没有办法告知Adblock Plus需要多少内存呀,这严重依赖于用户的设置。如果原来模块中内存小,那就很可能会自动创建带有不同堆的新的asm.js模块——而这样的话不同模块之间处理相同的数据就不会那么容易了。
最后,有些事情对于其他人可能没什么大不了的,但对于Adblock Plus却不是这样:这里不存在哈希表。在Adblock Plus不去使用它们是不可能的,因为它们是算法的重要组成部分。而这意味着哈希表需要在asm.js中重新实现。而我绝对不会认为那会有什么乐趣可言。
现在该怎么办呢?
所有这一切使得用asm.js来提升扩展的运行速度看起来似乎变成了一项复杂的挑战。此时很难想象Emscripten将会在扩展中得到许多的应用,因为生成的代码很容易变得很大,而字符串的转换则会损耗过多的性能。我所期望的工作应该是作为asm.js相对简单的超集的编译器,来打破asm.js的这些常规。使用手写的代码使得asm.js可用性增强并不需要太多东西:变量的类型声明,结构体类型和命名空间/对象看来就是最最重要的遗漏。一些通用辅助器可以来自动的管理堆,而使用哈希表则应该使得可用的代码被生产出来。但是,仍然不能有条件的分配内存在我看来则是另外一个需要在标准中明确的重要问题。