WebAssembly體驗之編碼base64(AssemblyScript使用教程)
前言
WebAssembly 不用多說懂的都懂,將運算函式通過 c++ 等編譯為二進位制的 .wasm
檔案後,再通過 JavaScript 的 WebAssembly Api 呼叫即可進行“快速”計算。
下面快速上手體驗一把,不使用 c++ 等編譯 .wasm
檔案。
WebAssembly 中文網:WebAssembly-CN
WebAssembly 英文官網:WebAssembly-EN
使用
AssemblyScript
AssemblyScript 是 WebAssembly 社群的一個 JavaScript 解決方案,通過編寫 TypeScript 來達到近似 C 語言的型別限制,完成預編譯,進而做到編譯出二進位制 .wasm
檔案。
AssemblyScript 官方專案:AssemblyScript / assemblyscript
那他與使用 c++ 等進行編譯有什麼限制呢,主要是兩點:
-
型別不全面。因為是通過 ts 給出近似實現,所以目前不可以使用複雜型別(比如
RegExp
,TextEncoder
等),且 h5 新 api 大部分也不能使用。 -
不可以使用第三方依賴。
對於第一點型別限制,由於是使用 ts 編寫所以不支援的型別會報錯還是很友好的,對於第二點,意味著所有功能都需要我們純手撕,另外很多 h5 api 用不了的情況下,更增加了複雜性。
搭建環境
官方文件:AssemblyScript
先初始化專案:
yarn init -y
安裝兩個基本依賴:
yarn add @assemblyscript/loader
yarn add -D assemblyscript
其中 @assemblyscript/loader
是微型載入器,可以幫我們省去很多配置,即開即用。assemblyscript
是開發核心,內建了所有可用的型別宣告(在 node_modules/assemblyscript/std
下)。
初始化專案:
yarn asinit .
此時會列印將要生成的檔案結構,輸入 Y
確認,將得到一個基礎開發目錄:
我們關注的只是在 assembly/index.ts
內函式編寫邏輯。
base64 邏輯實現
雖然 base64 是個小功能,但是痛點啪的一下就凸顯出來了,很快啊。
我們不能使用第三方庫,於是乎 js-base64
是不能用的。去把原始碼搬進來行不行?是不行的,因為 js-base64
使用了很多 h5 的 api 與 RegExp
正則替換,所以我們只能實現一個 乞丐版 的:編碼 base64:
// ./assembly/index.ts
export class Base64 {
_keyStr: string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
encode(input: string): string {
var output = "";
var chr1: i32, chr2: i32, chr3: i32, enc1: i32, enc2: i32, enc3: i32, enc4: i32;
var i = 0;
input = this._utf8_encode(input);
while (i < input.length) {
chr1 = input.charCodeAt(i++);
chr2 = input.charCodeAt(i++);
chr3 = input.charCodeAt(i++);
enc1 = chr1 >> 2;
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
enc4 = chr3 & 63;
if (isNaN(chr2)) {
enc3 = enc4 = 64;
}
else if (isNaN(chr3)) {
enc4 = 64;
}
output = output +
this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);
} // Whend
return output;
} // End Function encode
_utf8_encode(string: string): string {
var utftext = "";
for (var n = 0; n < string.length; n++) {
var c = string.charCodeAt(n);
if (c < 128) {
utftext += String.fromCharCode(c);
}
else if ((c > 127) && (c < 2048)) {
utftext += String.fromCharCode((c >> 6) | 192);
utftext += String.fromCharCode((c & 63) | 128);
}
else {
utftext += String.fromCharCode((c >> 12) | 224);
utftext += String.fromCharCode(((c >> 6) & 63) | 128);
utftext += String.fromCharCode((c & 63) | 128);
}
} // Next n
return utftext;
}
}
export function getBase64(): Base64 {
return new Base64()
}
語法規則
編寫 AssemblyScript 就要遵守他的規則,我們著重需要確定的是三個部分:
-
迴歸原始。不使用 h5 新 api (
atob
、TextEncoder
等),不接觸複雜型別(RegExp
等),如果編輯器紅線報錯就是找不到相對應的型別了,說明這個型別無法使用。 -
注意數字型別。對於 number 型別,AssemblyScript 含有更具體的
i32
、i64
、f32
等型別(見官方文件 Types ),在相應的變數初始化時對其指定合適的型別。 -
明確基本型別。變數初始化要指明其基礎型別,以便 AssemblyScript 預編譯。
到此為止,我們完成了最核心的 AssemblyScript 邏輯編寫。
此處需要我們注意兩個問題:
-
上面這個邏輯是簡單版的,比如換行等邊界情況是沒有考慮的。
-
為什麼要寫成單例模式,這麼寫是官方推薦的匯出類寫法,採用相同的引用節省資源。
構建 wasm
執行打包:
yarn asbuild
之後就會在 ./build
下得到優化後和優化前的 .wasm
二進位制檔案:
實際呼叫
在 ./test/index.js
內編寫:
const loader = require("@assemblyscript/loader");
const fs =require('fs')
const text = '年輕人不講武德'
loader.instantiate(
fs.readFileSync('../build/optimized.wasm'),
{env: { memory: new WebAssembly.Memory({initial:10, maximum:100})} }
).then(({ exports }) => {
console.time("測試 wasm 速度: ")
const { __getString, __retain, __newString, __release } = exports
const { getBase64, Base64 } = exports
const Base64Ptr = getBase64()
const base64 = Base64.wrap(Base64Ptr)
for(let i = 0;i<10000;i++) {
const textPtr = __retain(__newString(text))
const outputPtr = base64.encode(textPtr)
// console.log(__getString(outputPtr))
__release(textPtr)
__release(outputPtr)
}
__release(Base64Ptr)
console.timeEnd("測試 wasm 速度: ")
})
下面我們分塊講解:
loader.instantiate(
fs.readFileSync('../build/optimized.wasm'),
{env: { memory: new WebAssembly.Memory({initial:10, maximum:100})} }
)
作用:初始化一個微型載入器 loader ,他內建了很多預設,可以幫我們省去很多配置的功夫,往往我們只需要指定 env.memory
引數定義初始記憶體值即可(在這裡不指定也可),當你需要更大記憶體運算時,記得指定。
引數1:這裡第一個引數是讀入 .wasm
檔案,可以是 fs
本地讀取,也可以是 fetch
遠端拉取(在瀏覽器的情況)。
引數2:loader 的配置,正常情況可以不傳,採用預設配置,更多配置詳見 node_modules/@assemblyscript/loader/index.d.ts
中的 Imports
與官網說明。
.then(({ exports }) => {
console.time("測試 wasm 速度: ")
// ...
console.timeEnd("測試 wasm 速度: ")
})
拿到非同步載入的結果並結構得到 exports
,即為我們在 ./assembly/index.ts
匯出的函式。
注:實際上 exports
並不只有我們匯出的函式,他還有一些“輔助”函式,幫助我們更友好的使用 WebAssembly 。
// 匯出輔助函式
const { __getString, __retain, __newString, __release } = exports
// 匯出我們編寫的函式
const { getBase64, Base64 } = exports
// 獲取 class 單例的指標
const Base64Ptr = getBase64()
// 將指標轉為實際的 class 類
const base64 = Base64.wrap(Base64Ptr)
在這裡,我們先將輔助函式匯出,為什麼需要輔助函式?因為在 WebAssembly 中,不存在字串和 class 等基本型別的概念,一切均為 buffer 與 number ,所以輔助函式的作用就是幫我們做了 string 和 class 的中間轉化處理,真是非常友好了!
name | description |
---|---|
__getString | 從一個 string 的指標獲取實際的字串值 |
__retain | 定義一個引用 id ,以便後續回收,多次呼叫的 id 會進行累加(並不需要我們維護) |
__newString | 將實際字串轉為 string 的指標,以便傳入 |
__release | 根據 __retain 釋放引用 |
更多輔助函式請看官網說明:loader usage
// 執行 10000 次編碼
for(let i = 0;i<10000;i++) {
// 獲取一個 string 的指標
const textPtr = __retain(__newString(text))
// 傳入指標得到返回值 string 的指標
const outputPtr = base64.encode(textPtr)
// 可以通過 __getString 方法將 string 指標轉為實際的字串
// console.log(__getString(outputPtr))
// 清理引用
__release(textPtr)
__release(outputPtr)
}
__release(Base64Ptr)
我們對其做 10000
次編碼,實際中被編碼的 text
會發生變化,可能時間會更長:
校驗
我們列印一次出來看一下結果:
校驗:
成功!
對比
下面我們用相同的程式碼進行測試直接使用的速度:
const { getBase64 } = require('./encode')
console.time("測試直接使用速度")
const base = getBase64()
for(let i = 0;i<10000;i++) {
base.encode(text)
// console.log(base.encode(text))
}
console.timeEnd("測試直接使用速度")
結果:
總結
目前 WebAssembly 主要是應用在音視訊處理和網頁遊戲上,可以藉助相關庫的便利性,比如 ffmpeg 實現轉碼,音視訊壓縮,B 站視訊上傳過程即可選擇封面等。
加上規範的不成熟,舊版本瀏覽器相容問題,以及如此緩慢的速度,雖然不能否定,但也請不要太吹噓 WebAssembly 了。
相關文章
- 體驗WebAssemblyWeb
- Java之Base64編碼解析Java
- 計算機編碼規則之:Base64編碼計算機
- Java Base64編碼使用介紹Java
- Base64編碼
- base64 編碼原理
- Base64系列第二篇 python中使用Base64編碼解碼Python
- Base64 編碼的程式設計使用.md程式設計
- 將ttf檔案base64編碼後使用
- Base64 編碼解碼原理
- 從原理上搞定編碼-- Base64編碼
- 【字元編碼】字元編碼 && Base64編碼演算法字元演算法
- 001 Rust和WebAssembly初體驗RustWeb
- netty系列之:java中的base64編碼器NettyJava
- 使用 Rust + WebAssembly 編寫 crc32RustWeb
- Android資料加密之Base64編碼演算法Android加密演算法
- 【字元編碼系列】Base64編碼原理以及實現字元
- HanLP使用教程——NLP初體驗HanLP
- netty系列之:netty中的核心編碼器base64Netty
- rust實戰系列-base64編碼Rust
- Base64編碼知識詳解
- base64編碼原理和函式函式
- Base64編碼的全面介紹
- Base64編碼與解碼程式碼例項
- Java 8中的Base64編碼和解碼Java
- 用JS進行Base64編碼、解碼JS
- 【Java小工匠】密碼學--base64編碼Java密碼學
- CryptoAPI 對資料Base64編碼解碼API
- 體面編碼之JavaJava
- JWebAssembly:Java 位元組碼到 WebAssembly 編譯器WebJava編譯
- 體面編碼之程式碼提交
- JS 簡單實現UTF-8編碼,Base64編碼JS
- java RSA公私鑰與其base64編碼之間的轉換Java
- Base64自定義編碼表及破解
- base64編碼的原理及實現
- 深入瞭解圖片Base64編碼
- Rust中字串的base64編碼與解碼Rust字串
- PHP安全的URL字串base64編碼和解碼PHP字串