摘要:初體驗從零開始重構計算模塊從屬于筆者的前端入門與工程實踐,更多相關資料文章參考學習與實踐資料索引和學習與實踐資料索引。不過筆者也只是了解其概念而未真正付諸實踐,本文即是筆者在將我司某個簡單項目中的計算模塊重構為過程中的總結。
WebAssembly 初體驗:從零開始重構計算模塊從屬于筆者的 Web 前端入門與工程實踐,更多相關資料文章參考WebAssembly 學習與實踐資料索引和 React 學習與實踐資料索引。本文中使用的游戲代碼修改自 WebAssembly 101: a developer"s first steps。
WebAssembly 的概念、意義以及未來帶來的性能提升相信已是耳熟能詳,筆者在前端每周清單系列中也是經常會推薦 WebAssembly 相關文章。不過筆者也只是了解其概念而未真正付諸實踐,本文即是筆者在將我司某個簡單項目中的計算模塊重構為 WebAssembly 過程中的總結。在簡單的實踐中筆者個人感覺,WebAssembly 的抽象程度會比 JavaScript 高不少,未來對于大型項目的遷移,對于純前端工程師而言可能存在的坑也是不少,仿佛又回到了被指針統治的年代。本文筆者使用的案例已經集成到了 React 腳手架 create-react-boilerplate 中 ,可以方便大家快速本地實踐。
編譯環境搭建我們使用 Emscripten 將 C 代碼編譯為 wasm 格式,官方推薦的方式是首先下載 Portable Emscripten SDK for Linux and OS X (emsdk-portable.tar.gz) 然后利用 emsdk 進行安裝:
$ ./emsdk update $ ./emsdk install latest # 如果出現異常使用 ./emsdk install sdk-1.37.12-64bit # https://github.com/kripken/emscripten/issues/5272
安裝完畢后激活響應環境即可以進行編譯:
$ ./emsdk activate latest $ source ./emsdk_env.sh # you can add this line to your .bashrc
筆者在本地執行上述搭建步驟時一直失敗,因此改用了 Docker 預先配置好的鏡像進行處理:
# 拉取 Docker 鏡像 docker pull 42ua/emsdk # 執行編譯操作 docker run --rm -v $(pwd):/home/src 42ua/emsdk emcc hello_world.c
對應的 Dockfile 如下所示,我們可以自行修改以適應未來的編譯環境:
FROM ubuntu RUN apt-get update && apt-get install -y build-essential cmake python2.7 python nodejs-legacy default-jre git-core curl && apt-get clean && cd ~/ && curl -sL https://s3.amazonaws.com/mozilla-games/emscripten/releases/emsdk-portable.tar.gz | tar xz && cd emsdk-portable/ && ./emsdk update && ./emsdk install -j1 latest && ./emsdk activate latest && rm -rf ~/emsdk-portable/clang/tag-*/src && find . -name "*.o" -exec rm {} ; && find . -name "*.a" -exec rm {} ; && find . -name "*.tmp" -exec rm {} ; && find . -type d -name ".git" -prune -exec rm -rf {} ; && apt-get -y --purge remove curl git-core cmake && apt-get -y autoremove && apt-get clean # http://docs.docker.com/engine/reference/run/#workdir WORKDIR /home/src
到這里基本環境已經配置完畢,我們可以對簡單的 counter.c 進行編譯,源文件如下:
int counter = 100; int count() { counter += 1; return counter; }
編譯命令如下所示,如果本地安裝好了 emcc 則可以直接使用,否則使用 Docker 環境進行編譯:
$ docker run --rm -v $(pwd):/home/src 42ua/emsdk emcc counter.c -s WASM=1 -s SIDE_MODULE=1 -o counter.wasm $ emcc counter.c -s WASM=1 -s SIDE_MODULE=1 -o counter.wasm # 如果出現以下錯誤,則是由如下參數 # WebAssembly Link Error: import object field "DYNAMICTOP_PTR" is not a Number emcc counter.c -O1 -s WASM=1 -s SIDE_MODULE=1 -o counter.wasm
這樣我們就得到了 WebAssembly 代碼:
獨立的 .wasm 文件并不能直接使用,我們需要在客戶端中使用 JavaScript 代碼將其加載進來。最樸素的加載 WebAssembly 的方式就是使用 fetch 抓取然后編譯,整個過程可以封裝為如下函數:
// 判斷是否支持 WebAssembly if (!("WebAssembly" in window)) { alert("當前瀏覽器不支持 WebAssembly!"); } // Loads a WebAssembly dynamic library, returns a promise. // imports is an optional imports object function loadWebAssembly(filename, imports) { // Fetch the file and compile it return fetch(filename) .then(response => response.arrayBuffer()) .then(buffer => WebAssembly.compile(buffer)) .then(module => { // Create the imports for the module, including the // standard dynamic library imports imports = imports || {}; imports.env = imports.env || {}; imports.env.memoryBase = imports.env.memoryBase || 0; imports.env.tableBase = imports.env.tableBase || 0; if (!imports.env.memory) { imports.env.memory = new WebAssembly.Memory({ initial: 256 }); } if (!imports.env.table) { imports.env.table = new WebAssembly.Table({ initial: 0, element: "anyfunc" }); } // Create the instance. return new WebAssembly.Instance(module, imports); }); }
我們可以使用上述工具函數加載 wasm 文件:
loadWebAssembly("counter.wasm") .then(instance => { var exports = instance.exports; // the exports of that instance var count = exports. _count; // the "_count" function (note "_" prefix) // 下面即可以調用 count 函數 } );
而在筆者的腳手架中,使用了 wasm-loader 進行加載,這樣可以將 wasm 直接打包在 Bundle 中,然后通過 import 導入:
import React, { PureComponent } from "react"; import CounterWASM from "./counter.wasm"; import Button from "antd/es/button/button"; import "./Counter.scss"; /** * Description 簡單計數器示例 */ export default class Counter extends PureComponent { state = { count: 0 }; componentDidMount() { this.counter = new CounterWASM({ env: { memoryBase: 0, tableBase: 0, memory: new window.WebAssembly.Memory({ initial: 256 }), table: new window.WebAssembly.Table({ initial: 0, element: "anyfunc" }) } }); this.setState({ count: this.counter.exports._count() }); } /** * Description 默認渲染函數 */ render() { const isWASMSupport = "WebAssembly" in window; if (!isWASMSupport) { return (瀏覽器不支持 WASM); } return (簡單計數器示例: {this.state.count}); } }
在使用 wasm-loader 時,其會調用 new WebAssembly.Instance(module, importObject);
module 即 WebAssembly.Module 實例。
importObject 即默認的由 wasm-loader 提供的對象。
簡單游戲引擎重構上文我們討論了利用 WebAssembly 重構簡單的計數器模塊,這里我們以簡單的游戲為例,交互式的感受 WebAssembly 帶來的性能提升,可以直接查看游戲的在線演示。這里的游戲引擎即是執行部分計算與重新賦值操作,譬如這里的計算下一個位置狀態的函數在 C 中實現為:
EMSCRIPTEN_KEEPALIVE void computeNextState() { loopCurrentState(); int neighbors = 0; int i_m1, i_p1, i_; int j_m1, j_p1; int height_limit = height - 1; int width_limit = width - 1; for (int i = 1; i < height_limit; i++) { i_m1 = (i - 1) * width; i_p1 = (i + 1) * width; i_ = i * width; for (int j = 1; j < width_limit; j++) { j_m1 = j - 1; j_p1 = j + 1; neighbors = current[i_m1 + j_m1]; neighbors += current[i_m1 + j]; neighbors += current[i_m1 + j_p1]; neighbors += current[i_ + j_m1]; neighbors += current[i_ + j_p1]; neighbors += current[i_p1 + j_m1]; neighbors += current[i_p1 + j]; neighbors += current[i_p1 + j_p1]; if (neighbors == 3) { next[i_ + j] = 1; } else if (neighbors == 2) { next[i_ + j] = current[i_ + j]; } else { next[i_ + j] = 0; } } } memcpy(current, next, width * height); }
而對應的 JS 版本引擎的實現為:
computeNextState() { let neighbors, iM1, iP1, i_, jM1, jP1; this.loopCurrentState(); for (let i = 1; i < this._height - 1; i++) { iM1 = (i - 1) * this._width; iP1 = (i + 1) * this._width; i_ = i * this._width; for (let j = 1; j < this._width - 1; j++) { jM1 = j - 1; jP1 = j + 1; neighbors = this._current[iM1 + jM1]; neighbors += this._current[iM1 + j]; neighbors += this._current[iM1 + jP1]; neighbors += this._current[i_ + jM1]; neighbors += this._current[i_ + jP1]; neighbors += this._current[iP1 + jM1]; neighbors += this._current[iP1 + j]; neighbors += this._current[iP1 + jP1]; if (neighbors === 3) { this._next[i_ + j] = 1; } else if (neighbors === 2) { this._next[i_ + j] = this._current[i_ + j]; } else { this._next[i_ + j] = 0; } } } this._current.set(this._next); }
本部分的編譯依舊是直接將 [engine.c]() 編譯為 engine.wasm,不過在導入的時候我們需要動態地向 wasm 中注入外部函數:
this.module = new EngineWASM({ env: { memoryBase: 0, tableBase: 0, memory: new window.WebAssembly.Memory({ initial: 1024 }), table: new window.WebAssembly.Table({ initial: 0, element: "anyfunc" }), _malloc: size => { let buffer = new ArrayBuffer(size); return new Uint8Array(buffer); }, _memcpy: (source, target, size) => { let sourceEnd = source.byteLength; let i, j; for ( (i = 0), (j = 0), (k = new Uint8Array(target)), (l = new Uint8Array( source )); i < sourceEnd; ++i, ++j ) k[j] = l[i]; } } });
到這里文本告一段落,筆者最后需要聲明的是因為這只是隨手做的實驗,最后的代碼包括對于內存的操作可能存在潛在問題,請讀者批評指正。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/83454.html
摘要:前端每周清單專注前端領域內容,以對外文資料的搜集為主,幫助開發者了解一周前端熱點分為新聞熱點開發教程工程實踐深度閱讀開源項目巔峰人生等欄目。利用降低三倍加載速度自推出之后,很多開發者都開始嘗試在小型項目中實踐,不過尚缺大型真實案例比較。 前端每周清單專注前端領域內容,以對外文資料的搜集為主,幫助開發者了解一周前端熱點;分為新聞熱點、開發教程、工程實踐、深度閱讀、開源項目、巔峰人生等欄目...
摘要:前端日報精選漸進式動畫解決方案從前端開發看面向未來的敏捷學習法知乎專欄深度剖析現代應用眾成翻譯譯關于你需要知道的一切構建離線優先的應用知乎專欄中文為何默認開啟四進程不犧牲內存占用異步一淺出異步事件性能調優之內存篇二知乎專欄之性能 2017-06-16 前端日報 精選 漸進式動畫解決方案從前端開發看面向未來的敏捷學習法 - 知乎專欄深度剖析現代 JavaScript 應用 — SiteP...
摘要:前端每周清單專注前端領域內容,以對外文資料的搜集為主,幫助開發者了解一周前端熱點分為新聞熱點開發教程工程實踐深度閱讀開源項目巔峰人生等欄目。背后的故事本文是對于年之間世界發生的大事件的詳細介紹,闡述了從提出到角力到流產的前世今生。 前端每周清單專注前端領域內容,以對外文資料的搜集為主,幫助開發者了解一周前端熱點;分為新聞熱點、開發教程、工程實踐、深度閱讀、開源項目、巔峰人生等欄目。歡迎...
某熊的技術之路指北 ? 當我們站在技術之路的原點,未來可能充滿了迷茫,也存在著很多不同的可能;我們可能成為 Web/(大)前端/終端工程師、服務端架構工程師、測試/運維/安全工程師等質量保障、可用性保障相關的工程師、大數據/云計算/虛擬化工程師、算法工程師、產品經理等等某個或者某幾個角色。某熊的技術之路系列文章/書籍/視頻/代碼即是筆者蹣跚行進于這條路上的點滴印記,包含了筆者作為程序員的技術視野、...
閱讀 1778·2023-04-25 14:33
閱讀 3378·2021-11-22 15:22
閱讀 2177·2021-09-30 09:48
閱讀 2684·2021-09-14 18:01
閱讀 1740·2019-08-30 15:55
閱讀 3006·2019-08-30 15:53
閱讀 2139·2019-08-30 15:44
閱讀 648·2019-08-30 10:58