WebAssembly 與emscripten 入門指南

什么是WebAssembly?

  1. 是一個可移植、體積小缩焦、加載快并且兼容 Web 的全新格式
  2. wasm是體積小且加載快的二進制格式址儒, 其目標就是充分發(fā)揮硬件能力以達到原生執(zhí)行效率
  3. 運行在一個沙箱化的執(zhí)行環(huán)境中,甚至可以在現(xiàn)有的 JavaScript 虛擬機中實現(xiàn)敞葛。在web環(huán)境中,WebAssembly將會嚴格遵守同源策略以及瀏覽器安全策略。
  4. 中被設(shè)計成無版本毒租、特性可測試、向后兼容的漓骚。WebAssembly 可以被 JavaScript 調(diào)用蝌衔,進入 JavaScript 上下文榛泛,也可以像 Web API 一樣調(diào)用瀏覽器的功能。當然噩斟,WebAssembly 不僅可以運行在瀏覽器上曹锨,也可以運行在非web環(huán)境下。
  5. 支持語言: c/c++ 剃允、rust沛简、原始的webassembly S表達式文本、AssemblyScript(TypeScript-like)斥废、Go 椒楣。 其他語言(python,java,scala,kotlin等諸多語言也有工具實驗性支持)。

webassembly官網(wǎng)
mdn相關(guān)文檔

瀏覽器支持情況

image.png

webassembly 特性

  1. 計算速度快牡肉,性能高捧灰,編譯成wasm后的代碼性能接近原生
  2. 可以使用c++/c/go 眾多的三方庫來前端處理復雜任務(wù)與計算 (opencv、FFmpeg等)
  3. 不需要垃圾回收機制统锤,手動管理內(nèi)存
  4. 通過wasm的內(nèi)存與JavaScript 通信

應(yīng)用場景

  • 將 C毛俏、C++、Rust 等語言編寫的程序移植到瀏覽器
  • 圖形圖像處理領(lǐng)域(如OCR識別)饲窿,如頁游煌寇、數(shù)據(jù)可視化等
  • 音視頻編解碼識別、AI等等
  • 解壓逾雄、壓縮 等對性能要求高的需求

webassembly 基本概念及使用

幾個概念

  • Module 一個“代碼單元”阀溶。包含編譯好的二進制代碼⊙挥荆可以高效的緩存银锻、共享。未來可以像一個ES2015模塊一樣導入/導出
  • Memory 內(nèi)存辽故,連續(xù)的徒仓,可變大小的字節(jié)數(shù)組緩沖區(qū)√芄福可以理解為一個“堆”
  • Table 連續(xù)的掉弛,可變大小的類型數(shù)組緩沖區(qū) 現(xiàn)在table只支持函數(shù)引用類型,可以類比為一個“椢棺撸”
  • Instance 在Module基礎(chǔ)上殃饿,包含所有運行時所需狀態(tài)的實例,如果把Module類比為一個cpp文件芋肠,那么Instance就是鏈接了dll的exe文件

c代碼-->借助工具編譯為wasm

#include<stdio.h>
void fibonacci(int n)
{
 int first = 0, second = 1, next;
 for (int i = 0; i < n; i++)
 {
  next = first + second;
  first = second;
  second = next;
 }
}

load wasm 文件乎芳,獲取 instance 實例

function load(path) {
 return fetch(path)
  // 獲取二進制buffer
  .then(res => res.arrayBuffer())
  // 編譯&實例化,導入js對象
  .then(bytes => WebAssembly.instantiate(bytes, importObj))
  // 返回實例
  .then(res => res.instance)
}

從instance中獲取導出的文件

const fibonacci_wasm = instance.exports._fibonacci

上述代碼重復計算一百萬次斐波那契數(shù)列46項(47項會溢出),結(jié)果如下:
- C:3ms
- JS: 70ms
- WebAssembly:11ms

** 引用自 - [1] https://blog.csdn.net/m549393829/article/details/81839822

emscripten 封裝好了上述獲取實例的方法使用更簡單奈惑,如 emcc 編譯后的js文件 abc.js

import myModule from "../asm/abc.js";
myModule().then(zModule => {
      this.zModule = zModule;
  });
// 此時 zModule 就包含你導出的c方法及吭净,emscripten 導出的常用方法

使用Emscripten編譯并使用流程

  1. 安裝Emscripten 環(huán)境 (略) 詳見 emscripten官網(wǎng)
  2. 閱讀C/C++ 三方庫文檔
  3. 編寫C函數(shù),用于調(diào)用庫中的方法
  4. emcc命令編譯c語言為wasm 及 封裝的js膠水代碼
  5. 編寫膠水代碼肴甸,用于C語言與js通信寂殉,js中調(diào)用c函數(shù) (直接調(diào)用只能傳遞int值,傳遞其他類型值需要借助內(nèi)存處理)

Vue中使用

方式一: 將wasm 與 js文件放到如cdn或服務(wù)器
方式二: 將wasm 與 js封裝 為庫原在,發(fā)布到 npm使用
方式三: 本地使用友扰,

  • wasm文件并不會被webpack打包進dist,使用 url-loader 將 wasm 只作文靜態(tài)文件路徑 注意:import 下wasm文件確保被打包進dist
  • 可以放在vue的public文件下

Emscripten 編譯命令

emcc命令指引

emcc -
優(yōu)化flag庶柿,它們-O0村怪,-O1,-O2浮庐,-Os甚负,-Oz,-O3审残。 對應(yīng)不同優(yōu)化級別

-s OPTION=VALU 傳給編譯器的所有涉及到JavaScript代碼生成的選項

emcc simple/helloword.c -o output/hellow.js \                                             
-s EXTRA_EXPORTED_RUNTIME_METHODS="['cwrap', 'ccall','abc,'_malloc','_free']" \   #導出的函數(shù)腊敲,abc為自己寫的c語中的函數(shù),其他為emscriten自帶的                        
-s MODULARIZE=1   # 模塊化维苔,生成閉包函數(shù)
-s ENVIRONMENT="web" \
-s ALLOW_MEMORY_GROWTH=1 \  #開啟可變內(nèi)存
-s FORCE_FILESYSTEM=1   # 強制啟用em的虛擬文件系統(tǒng)
-s RESERVED_FUNCTION_POINTERS #保留函數(shù)表指針

JS 類型化數(shù)組 與 buffer,與Blob

ArrayBuffer是一個構(gòu)造函數(shù),可以分配一段可以存放數(shù)據(jù)的連續(xù)內(nèi)存區(qū)域

var buffer = new ArrayBuffer(16);  //創(chuàng)建一個連續(xù)16字節(jié)的內(nèi)存緩沖
視圖類型 說明 字節(jié)大小
Uint8Array 8位無符號整數(shù) 1字節(jié)
Int8Array 8位有符號整數(shù) 1字節(jié)
Uint8ClampedArray 8位無符號整數(shù)(溢出處理不同) 1字節(jié)
Uint16Array 16位無符號整數(shù) 2字節(jié)
Int16Array 16位有符號整數(shù) 2字節(jié)
Uint32Array 32位無符號整數(shù) 4字節(jié)
Int32Array 32位有符號整數(shù) 4字節(jié)
Float32Array 32位IEEE浮點數(shù) 4字節(jié)
Float64Array 64位IEEE浮點數(shù) 8字節(jié)
// 創(chuàng)建一個視圖懂昂,此視圖把緩沖內(nèi)的數(shù)據(jù)格式化為一個32位(4字節(jié))有符號整數(shù)數(shù)組
var int32View = new Int32Array(buffer);
// 我們可以像普通數(shù)組一樣訪問該數(shù)組中的元素
for (var i = 0; i < int32View.length; i++) {
  int32View[i] = i * 2;
}
  • Blob 對象表示一個不可變介时、原始數(shù)據(jù)的類文件對象。它的數(shù)據(jù)可以按文本或二進制的格式進行讀取凌彬,也可以轉(zhuǎn)換成 ReadableStream 來用于數(shù)據(jù)操作沸柔。

    Blob 表示的不一定是JavaScript原生格式的數(shù)據(jù)。File 接口基于Blob铲敛,繼承了 blob 的功能并將其擴展使其支持用戶系統(tǒng)上的文件褐澎。

var aBlob = new Blob( array, options )
/** 例如 */
const blob = new Blob([int32View], {
 type: "application/zip"
});
  • array 是一個由ArrayBuffer, ArrayBufferView, Blob, DOMString 等對象構(gòu)成的 Array ,或者其他類似對象的混合體伐蒋,它將會被放進 Blob工三。DOMStrings會被編碼為UTF-8。

  • options

    是一個可選的

    BlobPropertyBag
    

    字典先鱼,它可能會指定如下兩個屬性:

    • type俭正,默認值為 "",它代表了將會被放入到blob中的數(shù)組內(nèi)容的MIME類型焙畔。
    • endings掸读,默認值為"transparent",用于指定包含行結(jié)束符\n的字符串如何被寫入。 它是以下兩個值中的一個: "native"儿惫,代表行結(jié)束符會被更改為適合宿主操作系統(tǒng)文件系統(tǒng)的換行符澡罚,或者 "transparent",代表會保持blob中保存的結(jié)束符不變

1. 在C中調(diào)用JS函數(shù)之a(chǎn)ddFunction

Emscripten提供了多種在C環(huán)境調(diào)用JavaScript的方法肾请,包括:

  1. EM_JS/EM_ASM宏內(nèi)聯(lián)JavaScript代碼

  2. emscripten_run_script函數(shù)

  3. JavaScript函數(shù)注入(更準確的描述為:“Implement C API in JavaScript”留搔,既在JavaScript中實現(xiàn)C函數(shù)API)

  4. 使用addFunction將函數(shù)指針傳到C代碼中調(diào)用

    第一個字符表示函數(shù)的返回類型,其余字符表示參數(shù)類型

    • 'v': void type
    • 'i': 32-bit integer type
    • 'j': 64-bit integer type (currently does not exist in JavaScript)
    • 'f': 32-bit float type
    • 'd': 64-bit float type

☆☆☆ webassembly 與 c 的通信

js 與 c的通信主要借助 webassembly 中的內(nèi)存完成筐喳,基本思想是將一段數(shù)據(jù)的內(nèi)存地址與長度傳遞到C中催式,c根據(jù)地址和長度取出內(nèi)容。

ccall 與 ccwrap

如果直接使用C導出的函數(shù)避归,只能傳遞 number 的數(shù)據(jù)荣月,如果使用了其他類型的需要借助ccall/cwrap

以下摘自https://emscripten.org/docs/api_reference/preamble.js.html

ccall(ident,returnType梳毙,argTypes哺窄,args,opts )

從JavaScript調(diào)用已編譯的C函數(shù)账锹。

該函數(shù)從JavaScript執(zhí)行已編譯的C函數(shù)萌业,并返回結(jié)果。C ++名稱處理意味著無法調(diào)用“正臣榧恚”的C ++函數(shù)生年。該函數(shù)必須在.c文件中定義,或者是使用定義的C ++函數(shù)廓奕。extern "C"

returnType并argTypes讓您指定參數(shù)的類型和返回值抱婉。可能的類型是"number"桌粉,"string"蒸绩,"array",或"boolean"铃肯,其對應(yīng)于相應(yīng)的JavaScript類型患亿。使用"number"任何數(shù)值類型或C指針,string對于Cchar*表示字符串押逼,"boolean"對于一個布爾類型步藕,"array"為JavaScript陣列和類型數(shù)組,含有8位整數(shù)數(shù)據(jù)-即宴胧,數(shù)據(jù)被寫入的8位整數(shù)的C數(shù)組; 特別是如果您在此處提供類型化數(shù)組漱抓,則它必須是Uint8Array或Int8Array。如果要接收其他類型的數(shù)據(jù)數(shù)組恕齐,則可以手動分配內(nèi)存并對其進行寫入乞娄,然后在此處提供一個指針(作為"number"瞬逊,因為指針只是數(shù)字)。

// Call C from JavaScript
var result = Module.ccall('c_add', // name of C function
  'number', // return type
  ['number', 'number'], // argument types
  [10, 20]); // arguments

總結(jié): 傳遞的參數(shù) 只能為 字符串仪或,數(shù)字确镊,及Uint8Array或Int8Array

cwrap(ident,returnType范删,argTypes )

返回C函數(shù)的本機JavaScript包裝器蕾域。

這類似于,但是返回一個JavaScript函數(shù)到旦,該函數(shù)可以根據(jù)需要多次重復使用旨巷。C函數(shù)可以在C文件中定義,也可以是使用(防止名稱修改)定義的C兼容C ++函數(shù)添忘。ccall()extern "C"

// Call C from JavaScript
var c_javascript_add = Module.cwrap('c_add', // name of C function
  'number', // return type
  ['number', 'number']); // argument types

// Call c_javascript_add normally
console.log(c_javascript_add(10, 20)); // 30
console.log(c_javascript_add(20, 30)); // 50

emscripten cwrap 的膠水文件源碼如下

function cwrap(ident, returnType, argTypes, opts) {
  return function() {
    return ccall(ident, returnType, argTypes, arguments, opts);
  }
}

可以看出采呐,其本質(zhì) 還是ccall,只是返回了函數(shù)方便調(diào)用

ccall源碼如下

function ccall(ident, returnType, argTypes, args, opts) {
  // For fast lookup of conversion functions
  var toC = {
    'string': function(str) {
      var ret = 0;
      if (str !== null && str !== undefined && str !== 0) { // null string
        // at most 4 bytes per UTF-8 code point, +1 for the trailing '\0'
        var len = (str.length << 2) + 1;
        ret = stackAlloc(len);
        stringToUTF8(str, ret, len);
      }
      return ret;
    },
    'array': function(arr) {
      var ret = stackAlloc(arr.length);
      writeArrayToMemory(arr, ret);
      return ret;
    }
  };

  function convertReturnValue(ret) {
    if (returnType === 'string') return UTF8ToString(ret);
    if (returnType === 'boolean') return Boolean(ret);
    return ret;
  }

  var func = getCFunc(ident);
  var cArgs = [];
  var stack = 0;
  assert(returnType !== 'array', 'Return type should not be "array".');
  if (args) {
    for (var i = 0; i < args.length; i++) {
      var converter = toC[argTypes[i]];
      if (converter) {
        if (stack === 0) stack = stackSave();
        cArgs[i] = converter(args[i]);
      } else {
        cArgs[i] = args[i];
      }
    }
  }
  var ret = func.apply(null, cArgs);

  ret = convertReturnValue(ret);
  if (stack !== 0) stackRestore(stack);
  return ret;
}

可以看出搁骑,傳遞字符串及數(shù)組的本質(zhì)是 1.申請一定長度的空間(單位字節(jié))斧吐,得到空間的初始地址 2.將數(shù)據(jù)寫入內(nèi)存
接收數(shù)據(jù) 借助c中的指針(地址),從內(nèi)存取出仲器,

emscripten 封裝了一堆根據(jù)指針(地址) 從內(nèi)存中 寫入煤率、取出 字符串、文件 數(shù)據(jù)的放法乏冀,需要時自行文檔及源碼查閱蝶糯。

例子:傳遞復雜的數(shù)據(jù),如字符串數(shù)組到 c函數(shù)

循環(huán)申請空間辆沦,得到每個字符串的指針裳涛,并寫入內(nèi)存

  const nameList = ['ssdf','dsfsd','sdfs']; // 字符串數(shù)組
  const namePtrList = []; // 用于存放name指針
  nameList.forEach(v=>{
      const maxLen = nameList[i].length * 4 + 1; //c中字符串有 \0 為標志的結(jié)束符所以+1
      const namePtr = this.zModule._malloc(maxLen);
      namePtrList.push(namePtr);
      this.zModule.stringToUTF8(nameList[i], namePtr, maxLen); //emscripten 封裝好的寫入字符串到內(nèi)存的方法
  })

借助指針把namePtrList當做普通數(shù)組傳遞到c

/**
 * 傳遞數(shù)據(jù)的時候要借助上文提到的類型化數(shù)組,對應(yīng)大小的众辨,轉(zhuǎn)化為對應(yīng)的類型化數(shù)組
 * 這里指針(地址)是32位,且不需要符號舷礼,所以用 32位無符號的 Uint32Array
 */
const namePtrListArr = new Uint32Array(namePtrList); 
const namePtrListPtr = this.zModule._malloc(namePtrListArr.length * 4); // ...
/**
 * @type {Int8Array} - HEAP8
 * @type {Uint8Array} -HEAPU8
 * ... 同理
 */
this.zModule.HEAPU32.set(namePtrListArr, namePtrListPtr / 4); //寫入內(nèi)存鹃彻,第二個參數(shù)32位/4 ,16位/2 同理
// xxFun為c導出的函數(shù)
xxxFun(namePtrListPtr);

傳遞文件可以借助 emscripten的writefile 方法寫入 虛擬文件系統(tǒng),也可以將文件轉(zhuǎn)化為類型化數(shù)組借助指針寫入內(nèi)存妻献,方法同上

釋放空間

emscripten 導出的 _malloc,_free 用于申請及釋放空間蛛株,
在一段寫入內(nèi)存的數(shù)據(jù)不再使用后釋放空間,相當于垃圾回收

this.zModule._free(namePtrListPtr); // 釋放指針內(nèi)存

問題與優(yōu)化

內(nèi)存是非常珍貴的硬件資源育拨,用內(nèi)存模擬文件系統(tǒng)是非常奢侈的行為谨履。應(yīng)考慮減少內(nèi)存使用

2. [在web worker中使用webassembly](https://www.cntofu.com/book/150/zh/ch6-threads/ch6-02-sample.md

由于加載wasm的過程是同步耗時的,因此大的wasm文件可以借助web worker開啟多線程使用
Worker 接口是 Web Workers API 的一部分熬丧,指的是一種可由腳本創(chuàng)建的后臺任務(wù)笋粟,任務(wù)執(zhí)行中可以向其創(chuàng)建者收發(fā)信息

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子害捕,更是在濱河造成了極大的恐慌绿淋,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件尝盼,死亡現(xiàn)場離奇詭異吞滞,居然都是意外死亡,警方通過查閱死者的電腦和手機盾沫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門裁赠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人赴精,你說我怎么就攤上這事佩捞。” “怎么了祖娘?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵失尖,是天一觀的道長。 經(jīng)常有香客問我渐苏,道長掀潮,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任琼富,我火速辦了婚禮仪吧,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘鞠眉。我一直安慰自己薯鼠,他們只是感情好,可當我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布械蹋。 她就那樣靜靜地躺著出皇,像睡著了一般。 火紅的嫁衣襯著肌膚如雪哗戈。 梳的紋絲不亂的頭發(fā)上郊艘,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天,我揣著相機與錄音唯咬,去河邊找鬼纱注。 笑死,一個胖子當著我的面吹牛胆胰,可吹牛的內(nèi)容都是我干的狞贱。 我是一名探鬼主播,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蜀涨,長吁一口氣:“原來是場噩夢啊……” “哼瞎嬉!你這毒婦竟也來了蝎毡?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤佑颇,失蹤者是張志新(化名)和其女友劉穎顶掉,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體挑胸,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡痒筒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了茬贵。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片簿透。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖解藻,靈堂內(nèi)的尸體忽然破棺而出老充,到底是詐尸還是另有隱情,我是刑警寧澤螟左,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布啡浊,位于F島的核電站,受9級特大地震影響胶背,放射性物質(zhì)發(fā)生泄漏巷嚣。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一钳吟、第九天 我趴在偏房一處隱蔽的房頂上張望廷粒。 院中可真熱鬧,春花似錦红且、人聲如沸坝茎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嗤放。三九已至壁酬,卻和暖如春斤吐,著一層夾襖步出監(jiān)牢的瞬間厨喂,已是汗流浹背庄呈。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工蜕煌, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人诬留。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓贫母,卻偏偏與公主長得像盒刚,于是被迫代替她去往敵國和親腺劣。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容

  • WebAssembly 是一種可以使用非 JavaScript 編程語言編寫代碼并且能在瀏覽器上運行的技術(shù)方案因块。 ...
    hellomyshadow閱讀 373評論 0 0
  • webAssembly設(shè)計的目的不是為了手寫代碼而是為諸如C、C++和Rust等低級源語言提供一個高效的編譯目標涡上。...
    flyrain閱讀 2,259評論 0 0
  • 本次分享的文章是基于WebAssembly的探索與研究。最近需要做一個與加密相關(guān)的項目芋酌,想將后端的加密方案直接放到...
    Netwarps閱讀 1,591評論 1 1
  • 上一篇文章分享了WebAssembly概念和基本使用雁佳,通過兩個代碼示例的分析對WebAssembly有了大致的了解...
    Netwarps閱讀 1,856評論 0 0
  • 推薦指數(shù): 6.0 書籍主旨關(guān)鍵詞:特權(quán)、焦點堵腹、注意力、語言聯(lián)想秸滴、情景聯(lián)想 觀點: 1.統(tǒng)計學現(xiàn)在叫數(shù)據(jù)分析募判,社會...
    Jenaral閱讀 5,721評論 0 5