關于使用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可用性增強并不需要太多東西:變量的類型聲明,結構體類型和命名空間/對象看來就是最最重要的遺漏。一些通用輔助器可以來自動的管理堆,而使用哈希表則應該使得可用的代碼被生產出來。但是,仍然不能有條件的分配內存在我看來則是另外一個需要在標準中明確的重要問題。