国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

悄悄掀起 WebAssembly 的神秘面紗

qc1iu / 3247人閱讀

摘要:在拿到這塊內(nèi)存后,是擁有完全操作的權(quán)利的。后面定義了一個(gè)函數(shù),并導(dǎo)出為函數(shù)。首先,使用在棧內(nèi)壓入一個(gè)位整型常數(shù),然后使用在棧內(nèi)壓入一個(gè)位整型常數(shù),之后調(diào)用指令,這個(gè)指

前端開(kāi)發(fā)人員想必對(duì)現(xiàn)代瀏覽器都已經(jīng)非常熟悉了吧?HTML5,CSS4,JavaScript ES6,這些已經(jīng)在現(xiàn)代瀏覽器中慢慢普及的技術(shù)為前端開(kāi)發(fā)帶來(lái)了極大的便利。

得益于 JIT(Just-in-time)技術(shù),JavaScript 的運(yùn)行速度比原來(lái)快了 10 倍,這也是 JavaScript 被運(yùn)用得越來(lái)越廣泛的原因之一。但是,這是極限了嗎?

隨著瀏覽器技術(shù)的發(fā)展,Web 游戲眼看著又要“卷土重來(lái)”了,不過(guò)這一次不是基于 Flash 的游戲,而是充分利用了現(xiàn)代 HTML5 技術(shù)實(shí)現(xiàn)。JavaScript 成為了 Web 游戲的開(kāi)發(fā)語(yǔ)言,但是對(duì)于游戲這樣需要大量運(yùn)算的程序來(lái)說(shuō),即便是有 JIT 加持,JavaScript 的性能還是不能滿足人類貪婪的欲望。

JavaScript 在瀏覽器中是怎么跑起來(lái)的?

對(duì)于現(xiàn)在的計(jì)算機(jī)來(lái)說(shuō),它們只能讀懂“機(jī)器語(yǔ)言”,而人類的大腦能力有限,直接編寫機(jī)器語(yǔ)言難度有點(diǎn)大,為了能讓人更方便地編寫程序,人類發(fā)明了大量的“高級(jí)編程語(yǔ)言”,JavaScript 就屬于其中特殊的一種。

為什么說(shuō)是特殊的一種呢?由于計(jì)算機(jī)并不認(rèn)識(shí)“高級(jí)編程語(yǔ)言”寫出來(lái)的東西,所以大部分“高級(jí)編程語(yǔ)言”在寫好以后都需要經(jīng)過(guò)一個(gè)叫做“編譯”的過(guò)程,將“高級(jí)編程語(yǔ)言”翻譯成“機(jī)器語(yǔ)言”,然后交給計(jì)算機(jī)來(lái)運(yùn)行。但是,JavaScript 不一樣,它沒(méi)有“編譯”的過(guò)程,那么機(jī)器是怎么認(rèn)識(shí)這種語(yǔ)言的呢?

實(shí)際上,JavaScript 與其他一部分腳本語(yǔ)言采用的是一種“邊解釋邊運(yùn)行”的姿勢(shì)來(lái)運(yùn)行的,將代碼一點(diǎn)一點(diǎn)地翻譯給計(jì)算機(jī)。

那么,JavaScript 的“解釋”與其他語(yǔ)言的“編譯”有什么區(qū)別呢?不都是翻譯成“機(jī)器語(yǔ)言”嗎?簡(jiǎn)單來(lái)講,“編譯”類似于“全文翻譯”,就是代碼編寫好后,一次性將所有代碼全部編譯成“機(jī)器語(yǔ)言”,然后直接交給計(jì)算機(jī);而“解釋”則類似于“實(shí)時(shí)翻譯”,代碼寫好后不會(huì)翻譯,運(yùn)行到哪,翻譯到哪。

“解釋”和“編譯”兩種方法各有利弊。使用“解釋”的方法,程序編寫好后就可以直接運(yùn)行了,而使用“編譯”的方法,則需要先花費(fèi)一段時(shí)間等待整個(gè)代碼編譯完成后才可以執(zhí)行。這樣一看似乎是“解釋”的方法更快,但是如果一段代碼要執(zhí)行多次,使用“解釋”的方法,程序每次運(yùn)行時(shí)都需要重新“解釋”一遍,而“編譯”的方法則不需要了。這樣一看,“編譯”的整體效率似乎更高,因?yàn)樗肋h(yuǎn)只翻譯一次,而“解釋”是運(yùn)行一次翻譯一次。并且,“編譯”由于是一開(kāi)始就對(duì)整個(gè)代碼進(jìn)行的,所以可以對(duì)代碼進(jìn)行針對(duì)性的優(yōu)化。

JavaScript 是使用“解釋”的方案來(lái)運(yùn)行的,這就造成了它的效率低下,因?yàn)榇a每運(yùn)行一次都要翻譯一次,如果一個(gè)函數(shù)被循環(huán)調(diào)用了 10 次、100 次,這個(gè)執(zhí)行效率可想而知。

好在聰明的人類發(fā)明了 JIT(Just-in-time)技術(shù),它綜合了“解釋”與“編譯”的優(yōu)點(diǎn),它的原理實(shí)際上就是在“解釋”運(yùn)行的同時(shí)進(jìn)行跟蹤,如果某一段代碼執(zhí)行了多次,就會(huì)對(duì)這一段代碼進(jìn)行編譯優(yōu)化,這樣,如果后續(xù)再運(yùn)行到這一段代碼,則不用再解釋了。

JIT 似乎是一個(gè)好東西,但是,對(duì)于 JavaScript 這種動(dòng)態(tài)數(shù)據(jù)類型的語(yǔ)言來(lái)說(shuō),要實(shí)現(xiàn)一個(gè)完美的 JIT 非常難。為什么呢?因?yàn)?JavaScript 中的很多東西都是在運(yùn)行的時(shí)候才能確定的。比如我寫了一行代碼:const sum = (a, b, c) => a + b + c;,這是一個(gè)使用 ES6 語(yǔ)法編寫的 JavaScript 箭頭函數(shù),可以直接放在瀏覽器的控制臺(tái)下運(yùn)行,這將聲明一個(gè)叫做 sum 的函數(shù)。然后我們可以直接調(diào)用它,比如:console.log(sum(1, 2, 3)),任何一個(gè)合格的前端開(kāi)發(fā)人員都能很快得口算出答案,這將輸出一個(gè)數(shù)字 6。但是,如果我們這樣調(diào)用呢:console.log(sum("1", 2, 3)),第一個(gè)參數(shù)變成了一個(gè)字符串,這在 JavaScript 中是完全允許的,但是這時(shí)得到的結(jié)果就完全不同了,這會(huì)導(dǎo)致一個(gè)字符串和兩個(gè)數(shù)字進(jìn)行連接,得到 "123"。這樣一來(lái),針對(duì)這一個(gè)函數(shù)的優(yōu)化就變得非常困難了。

雖說(shuō) JavaScript 自身的“特性”為 JIT 的實(shí)現(xiàn)帶來(lái)了一些困難,但是不得不說(shuō) JIT 還是為 JavaScript 帶來(lái)了非常可觀的性能提升。

WebAssembly

為了能讓代碼跑得更快,WebAssembly 出現(xiàn)了(并且現(xiàn)在主流瀏覽器也都開(kāi)始支持了),它能夠允許你預(yù)先使用“編譯”的方法將代碼編譯好后,直接放在瀏覽器中運(yùn)行,這一步就做得比較徹底了,不再需要 JIT 來(lái)動(dòng)態(tài)得進(jìn)行優(yōu)化了,所有優(yōu)化都可以在編譯的時(shí)候直接確定。

WebAssembly 到底是什么呢?

首先,它不是直接的機(jī)器語(yǔ)言,因?yàn)槭澜缟系臋C(jī)器太多了,它們都說(shuō)著不同的語(yǔ)言(架構(gòu)不同),所以很多情況下都是為各種不同的機(jī)器架構(gòu)專門生成對(duì)應(yīng)的機(jī)器代碼。但是要為各種機(jī)器都生成的話,太復(fù)雜了,每種語(yǔ)言都要為每種架構(gòu)編寫一個(gè)編譯器。為了簡(jiǎn)化這個(gè)過(guò)程,就有了“中間代碼(Intermediate representation,IR)”,只要將所有代碼都翻譯成 IR,再由 IR 來(lái)統(tǒng)一應(yīng)對(duì)各種機(jī)器架構(gòu)。

實(shí)際上,WebAssembly 和 IR 差不多,就是用于充當(dāng)各種機(jī)器架構(gòu)翻譯官的角色。WebAssembly 并不是直接的物理機(jī)器語(yǔ)言,而是抽象出來(lái)的一種虛擬的機(jī)器語(yǔ)言。從 WebAssembly 到機(jī)器語(yǔ)言雖說(shuō)也需要一個(gè)“翻譯”過(guò)程,但是在這里的“翻譯”就沒(méi)有太多的套路了,屬于機(jī)器語(yǔ)言到機(jī)器語(yǔ)言的翻譯,所以速度上已經(jīng)非常接近純機(jī)器語(yǔ)言了。

這里有一個(gè) WebAssembly 官網(wǎng)上提供的 Demo,是使用 [Unity] 開(kāi)發(fā)并發(fā)布為 WebAssembly 的一個(gè)小游戲:https://webassembly.org/demo/,可以去體驗(yàn)體驗(yàn)。
.wasm 文件 與 .wat 文件

WebAssembly 是通過(guò) *.wasm 文件進(jìn)行存儲(chǔ)的,這是編譯好的二進(jìn)制文件,它的體積非常的小。

在瀏覽器中,提供了一個(gè)全局的 window.WebAssembly 對(duì)象,可以用于實(shí)例化 WASM 模塊。

WebAssembly 是一種“虛擬機(jī)器語(yǔ)言”,所以它也有對(duì)應(yīng)的“匯編語(yǔ)言”版本,也就是 *.wat 文件,這是 WebAssembly 模塊的文本表示方法,采用“S-表達(dá)式(S-Expressions)”進(jìn)行描述,可以直接通過(guò)工具將 *.wat 文件編譯為 *.wasm 文件。熟悉 [LISP] 的同學(xué)可能對(duì)這種表達(dá)式語(yǔ)法比較熟悉。

一個(gè)非常簡(jiǎn)單的例子

我們來(lái)看一個(gè)非常簡(jiǎn)單的例子,這個(gè)已經(jīng)在 Chrome 69 Canary 和 Chrome 70 Canary 中測(cè)試通過(guò),理論上可以在所有已經(jīng)支持 WebAssembly 的瀏覽器中運(yùn)行。(在后文中有瀏覽器的支持情況)

首先,我們先使用 S-表達(dá)式 編寫一個(gè)十分簡(jiǎn)單的程序:

;; test.wat
(module
  (import "env" "mem" (memory 1)) ;; 這里指定了從 env.mem 中導(dǎo)入一個(gè)內(nèi)存對(duì)象
  (func (export "get") (result i32)  ;; 定義并導(dǎo)出一個(gè)叫做“get”的函數(shù),這個(gè)函數(shù)擁有一個(gè) int32 類型的返回值,沒(méi)有參數(shù)
    memory.size))  ;; 最終返回 memory 對(duì)象的“尺寸”(單位為“頁(yè)”,目前規(guī)定 1 頁(yè) = 64 KiB = 65536 Bytes)
可以使用 [wabt] 中的 [wasm2wat] 工具將 wasm 文件轉(zhuǎn)為使用“S-表達(dá)式”進(jìn)行描述的 wat 文件。同時(shí)也可以使用 [wat2wasm] 工具將 wat 轉(zhuǎn)為 wasm。

在 wat 文件中,雙分號(hào) ;; 開(kāi)頭的內(nèi)容都是注釋。

上面這個(gè) wat 文件定義了一個(gè) module,并導(dǎo)入了一個(gè)內(nèi)存對(duì)象,然后導(dǎo)出了一個(gè)叫做“get”的函數(shù),這個(gè)函數(shù)返回當(dāng)前內(nèi)存的“尺寸”。

在 WebAssembly 中,線性內(nèi)存可以在內(nèi)部直接定義然后導(dǎo)出,也可以從外面導(dǎo)入,但是最多只能擁有一個(gè)內(nèi)存。這個(gè)內(nèi)存的大小并不是固定的,只需要給一個(gè)初始大小 initial,后期還可以根據(jù)需要調(diào)用 grow 函數(shù)進(jìn)行擴(kuò)展,也可以指定最大大小 maximum(這里所有內(nèi)存大小的單位都是“頁(yè)”,目前規(guī)定的是 1 頁(yè) = 64 KiB = 65536 Bytes。)

上面這個(gè) wat 文件使用 [wat2wasm] 編譯為 wasm 后生成的文件體積非常小,只有 50 Bytes:

$ wat2wasm test.wat
$ xxd test.wasm
00000000: 0061 736d 0100 0000 0105 0160 0001 7f02  .asm.......`....
00000010: 0c01 0365 6e76 036d 656d 0200 0103 0201  ...env.mem......
00000020: 0007 0701 0367 6574 0000 0a06 0104 003f  .....get.......?
00000030: 000b                                     ..

為了讓這個(gè)程序能在瀏覽器中運(yùn)行,我們還必須使用 JavaScript 編寫一段“膠水代碼(glue code)”,以便這個(gè)程序能被加載到瀏覽器中并執(zhí)行:

// main.js

const file = await fetch("./test.wasm");
const memory = new window.WebAssembly.Memory({ initial: 1 });
const mod = await window.WebAssembly.instantiateStreaming(file, {
  env: {
    mem: memory,
  },
});
let result;
result = mod.instance.exports.get();  // 調(diào)用 WebAssembly 模塊導(dǎo)出的 get 函數(shù)
console.log(result);  // 1
memory.grow(2);
result = mod.instance.exports.get();  // 調(diào)用 WebAssembly 模塊導(dǎo)出的 get 函數(shù)
console.log(result);  // 3

這里我使用了現(xiàn)代瀏覽器都已經(jīng)支持的 ES6 語(yǔ)法,首先,使用瀏覽器原生提供的 fetch 函數(shù)加載我們編譯好的 test.wasm 文件。注意,這里根據(jù)規(guī)范,HTTP 響應(yīng)的 Content-Type 中指定的 MIME 類型必須為 application/wasm

接下來(lái),我們 new 了一個(gè) WebAssembly.Memory 對(duì)象,通過(guò)這個(gè)對(duì)象,可以實(shí)現(xiàn) JavaScript 與 WebAssembly 之間互通數(shù)據(jù)。

再接下來(lái),我們使用了 WebAssembly.instantiateStreaming 來(lái)實(shí)例化加載的 WebAssembly 模塊,這里第一個(gè)參數(shù)是一個(gè) Readable Stream,第二個(gè)參數(shù)是 importObject,用于指定導(dǎo)入 WebAssembly 的結(jié)構(gòu)。因?yàn)樯厦娴?wat 代碼中指定了要從 env.mem 導(dǎo)入一個(gè)內(nèi)存對(duì)象,所以這里就得要將我們 new 出來(lái)的內(nèi)存對(duì)象放到 env.mem 中。

WebAssembly 還提供了一個(gè) instantiate 函數(shù),這個(gè)函數(shù)的第一個(gè)參數(shù)可以提供一個(gè) [ArrayBuffer] 或是 [TypedArray]。但是這個(gè)函數(shù)是不推薦使用的,具體原因做過(guò)流量代理轉(zhuǎn)發(fā)的同學(xué)可能會(huì)比較清楚,這里就不具體解釋了。

最后,我們就可以調(diào)用 WebAssembly 導(dǎo)出的函數(shù) get 了,首先輸出的內(nèi)容為 memoryinitial 的值。然后我們調(diào)用了 memory.grow 方法來(lái)增長(zhǎng) memory 的尺寸,最后輸出的內(nèi)容就是增長(zhǎng)后內(nèi)存的大小 1 + 2 = 3

一個(gè) WebAssembly 與 JavaScript 數(shù)據(jù)互通交互的例子

在 WebAssembly 中有一塊內(nèi)存,這塊內(nèi)存可以是內(nèi)部定義的,也可以是從外面導(dǎo)入的,如果是內(nèi)部定義的,則可以通過(guò) export 進(jìn)行導(dǎo)出。JavaScript 在拿到這塊“內(nèi)存”后,是擁有完全操作的權(quán)利的。JavaScript 使用 [DataView] 對(duì) Memory 對(duì)象進(jìn)行包裝后,就可以使用 DataView 下面的函數(shù)對(duì)內(nèi)存對(duì)象進(jìn)行讀取或?qū)懭氩僮鳌?/p>

這里是一個(gè)簡(jiǎn)單的例子:

;; example.wat
(module
  (import "env" "mem" (memory 1))
  (import "js" "log" (func $log (param i32)))
  (func (export "example")
    i32.const 0
    i64.const 8022916924116329800
    i64.store
    (i32.store (i32.const 8) (i32.const 560229490))
    (call $log (i32.const 0))))

這個(gè)代碼首先從 env.mem 導(dǎo)入一個(gè)內(nèi)存對(duì)象作為默認(rèn)內(nèi)存,這和前面的例子是一樣的。

然后從 js.log 導(dǎo)入一個(gè)函數(shù),這個(gè)函數(shù)擁有一個(gè) 32 位整型的參數(shù),不需要返回值,在 wat 內(nèi)部被命名為“$log”,這個(gè)名字只存在于 wat 文件中,在編譯為 wasm 后就不存在了,只存儲(chǔ)一個(gè)偏移地址。

后面定義了一個(gè)函數(shù),并導(dǎo)出為“example”函數(shù)。在 WebAssembly 中,函數(shù)里的內(nèi)容都是在棧上的。

首先,使用 i32.const 0 在棧內(nèi)壓入一個(gè) 32 位整型常數(shù) 0,然后使用 i64.const 8022916924116329800 在棧內(nèi)壓入一個(gè) 64 位整型常數(shù) 8022916924116329800,之后調(diào)用 i64.store 指令,這個(gè)指令將會(huì)將棧頂部第一個(gè)位置的一個(gè) 64 位整數(shù)存儲(chǔ)到棧頂部第二個(gè)位置指定的“內(nèi)存地址”開(kāi)始的連續(xù) 8 個(gè)字節(jié)空間中。

簡(jiǎn)而言之,就是在內(nèi)存的第 0 個(gè)位置開(kāi)始的連續(xù) 8 個(gè)字節(jié)的空間里,存入一個(gè) 64 位整型數(shù)字 8022916924116329800。這個(gè)數(shù)字轉(zhuǎn)為 16 進(jìn)制表示為:0x 6f 57 20 6f 6c 6c 65 48,但是由于 WebAssembly 中規(guī)定的[字節(jié)序]是使用“小端序(Little-Endian Byte Order)”來(lái)存儲(chǔ)數(shù)據(jù),所以,在內(nèi)存中第 0 個(gè)位置存儲(chǔ)的是 0x48,第 1 個(gè)位置存儲(chǔ)的是 0x65……所以,最終存儲(chǔ)的實(shí)際上是 0x 48 65 6c 6c 6f 20 57 6f,對(duì)應(yīng)著 [ASCII] 碼為:"Hello Wo"。

然后,后面的一句指令 (i32.store (i32.const 8) (i32.const 560229490)) 的格式是上面三條指令的“S-表達(dá)式”形式,只不過(guò)這里換成了 i32.store 來(lái)存儲(chǔ)一個(gè) 32 位整型常數(shù) 560229490 到 8 號(hào)“內(nèi)存地址”開(kāi)始的連續(xù) 4 個(gè)字節(jié)空間中。

實(shí)際上這一句指令的寫法寫成上面三句的語(yǔ)法是完全等效的:

i32.const 8
i32.const 560229490
i32.store

類似的,這里是在內(nèi)存的第 8 個(gè)位置開(kāi)始的連續(xù) 4 個(gè)字節(jié)的空間里,存入一個(gè) 32 位整型數(shù)字 560229490。這個(gè)數(shù)字轉(zhuǎn)為 16 進(jìn)制表示位:0x 21 64 6c 72,同樣采用“小端序”來(lái)存儲(chǔ),所以存儲(chǔ)的實(shí)際上是 0x 72 6c 64 21,對(duì)應(yīng)著 [ASCII] 碼為:"rld!"。

所以,最終,內(nèi)存中前 12 個(gè)字節(jié)中的數(shù)據(jù)為 0x 48 65 6c 6c 6f 20 57 6f 72 6c 64 21,連起來(lái)就是對(duì)應(yīng)著 [ASCII] 碼:"Hello World!"。

將這個(gè) wat 編譯為 wasm 后,文件大小為 95 Bytes:

$ wat2wasm example.wat
$ xxd example.wasm
00000000: 0061 736d 0100 0000 0108 0260 017f 0060  .asm.......`...`
00000010: 0000 0215 0203 656e 7603 6d65 6d02 0001  ......env.mem...
00000020: 026a 7303 6c6f 6700 0003 0201 0107 0b01  .js.log.........
00000030: 0765 7861 6d70 6c65 0001 0a23 0121 0041  .example...#.!.A
00000040: 0042 c8ca b1e3 f68d c8ab ef00 3703 0041  .B..........7..A
00000050: 0841 f2d8 918b 0236 0200 4100 1000 0b    .A.....6..A....

接下來(lái),還是使用 JavaScript 編寫“膠水代碼”:

// example.js

const file = await fetch("./example.wasm");
const memory = new window.WebAssembly.Memory({ initial: 1 });
const dv = new DataView(memory);
const log = offset => {
  let length = 0;
  let end = offset;
  while(end < dv.byteLength && dv.getUint8(end) > 0) {
    ++length;
    ++end;
  }
  if (length === 0) {
    console.log("");
    return;
  }
  const buf = new ArrayBuffer(length);
  const bufDv = new DataView(buf);
  for (let i = 0, p = offset; p < end; ++i, ++p) {
    bufDv.setUint8(i, dv.getUint8(p));
  }
  const result = new TextDecoder("utf-8").decode(buf);
  console.log(result);
};
const mod = await window.WebAssembly.instantiateStreaming(file, {
  env: {
    mem: memory,
  },
  js: { log },
});
mod.instance.exports.example();  // 調(diào)用 WebAssembly 模塊導(dǎo)出的 example 函數(shù)

這里,使用 DataView 對(duì) memory 進(jìn)行了一次包裝,這樣就可以方便地對(duì)內(nèi)存對(duì)象進(jìn)行讀寫操作了。

然后,這里在 JavaScript 中實(shí)現(xiàn)了一個(gè) log 函數(shù),函數(shù)接受一個(gè)參數(shù)(這個(gè)參數(shù)在上面的 wat 中指定了是整數(shù)型)。下面的實(shí)現(xiàn)首先是確定輸出的字符串長(zhǎng)度(字符串通常以 "