惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Security Latest
Security Latest
量子位
博客园 - 三生石上(FineUI控件)
小众软件
小众软件
S
SegmentFault 最新的问题
The GitHub Blog
The GitHub Blog
AWS News Blog
AWS News Blog
T
Threat Research - Cisco Blogs
博客园 - Franky
Vercel News
Vercel News
H
Help Net Security
Martin Fowler
Martin Fowler
Security Archives - TechRepublic
Security Archives - TechRepublic
L
LINUX DO - 热门话题
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
L
Lohrmann on Cybersecurity
Cyberwarzone
Cyberwarzone
W
WeLiveSecurity
V2EX - 技术
V2EX - 技术
C
CERT Recently Published Vulnerability Notes
S
Secure Thoughts
C
Cyber Attacks, Cyber Crime and Cyber Security
B
Blog RSS Feed
H
Hacker News: Front Page
P
Proofpoint News Feed
博客园 - 聂微东
N
News and Events Feed by Topic
C
Cybersecurity and Infrastructure Security Agency CISA
D
Docker
博客园_首页
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
人人都是产品经理
人人都是产品经理
The Hacker News
The Hacker News
S
Security @ Cisco Blogs
博客园 - 【当耐特】
F
Fortinet All Blogs
The Register - Security
The Register - Security
A
About on SuperTechFans
D
Darknet – Hacking Tools, Hacker News & Cyber Security
S
Schneier on Security
NISL@THU
NISL@THU
Attack and Defense Labs
Attack and Defense Labs
Help Net Security
Help Net Security
Cisco Talos Blog
Cisco Talos Blog
月光博客
月光博客
IT之家
IT之家
有赞技术团队
有赞技术团队
Know Your Adversary
Know Your Adversary
Hugging Face - Blog
Hugging Face - Blog

卢贤泼的博客

漫长的告别 04: 答案在别处 | 卢贤泼的博客 漫长的告别 03: 人生修炼 | 卢贤泼的博客 漫长的告别 02: 赢过昨天,输给明天 | 卢贤泼的博客 漫长的告别 01: 不期而遇 | 卢贤泼的博客 漫长的告别 00: 创刊号 | 卢贤泼的博客 2020 这一年 | 卢贤泼的博客 热血合唱团 | 卢贤泼的博客 我的 macOS 常用工具和配置 | 卢贤泼的博客 《The Social Dilemma》智能陷阱 | 卢贤泼的博客 《目光》:向光而行 | 卢贤泼的博客 1024 的周末 | 卢贤泼的博客 纪录片《人生果实》 | 卢贤泼的博客 读书笔记:小狗钱钱 | 卢贤泼的博客 国庆假期的一些小事 | 卢贤泼的博客 这个博客背后的技术细节 | 卢贤泼的博客 多重身份 | 卢贤泼的博客 一往无前 | 卢贤泼的博客 Python for JavaScript Developer | 卢贤泼的博客 2019 这一年 | 卢贤泼的博客 VueConf 2019 | 卢贤泼的博客 2018 这一年 | 卢贤泼的博客 伙伴,路途上的星光 | 卢贤泼的博客 《黑客与画家》--- 一个程序员的自我修养 | 卢贤泼的博客
WebAssembly With Rust | 卢贤泼的博客
alanlupublic@gmail.com (卢贤泼) · 2021-01-10 · via 卢贤泼的博客

4 大主流浏览器已经都支持了 WebAssembly,同时也很多项目中得到了实际应用,本文将分享如何构建一个 WebAssembly (简称 WASM) 以及在浏览器中的使用。

使用 Rust 构建一个 WASM

try-rust_ScreenShot2021-01-10at12.54.29PM

能够构建 WASM 的语言有很多,本文将使用 Rust 来构建,分享如何从 0 构建一个 WASM。

基础环境初始化

  1. 安装 Rust 环境
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
  1. 初始化一个 WASM 项目
cargo new --lib hello-wasm

更多详细的步骤可以看:Rust 和 WebAssembly 用例

如何将 Rust 构建为 WASM

Rust 社区中提供了封装好的工具wasm-pack,可以直接将 rust 的代码打包为一个 npm 包

在 Rust 中调用 JS 中的方法可以通过 wasm-bindgen

在做完了以上的一些方法之后,我们可以运行下面的命令,借助 wasm-pack 将 Rust 打包为一个 WebAssembly

wasm-pack build -t web

打包之后我们会得到得到一个新的 pkg 目录,里面有对应的构建产物,我们可以直接在 Web 中使用,下面是一段代码示例

<script type="module">
  import init, { greet } from './pkg/hello_wasm.js'
  init().then(() => {
    greet('WASM')
  })
</script>

WASM 是如何和 Web 进行交互的

经过上面的步骤,整体跑起来了 Hello World,上面相应的代码在这个仓库中 luxp/code-parctice/hello-wasm

下面我们从源码的角度,看看 WASM 是如何工作的

加载 WASM

wasm-pack 已经直接帮我们打包好了对应的模块,可以直接在浏览器中是用原生 import

<script type="module">
  import init, { greet } from './pkg/hello_wasm.js'
  init().then(() => {
    greet('WASM')
  })
</script>

pkg/hello_wasm.js

实际加载 WASM 的是在 pkg/hello_wasm.js 这个文件中,整体的流程可以简化以下的过程。 WebAssembly.instantiate 文档

fetch('hello_wasm_bg.wasm').then(async (module) => {
  let imports = {}
  let wasm = await WebAssembly.instantiate(module, imports)

  // wasm 这个就是 WebAssembly 的实例了
  return wasm
})

调用 WASM 中的方法

我们并不能直接调用 wasm 中的方法,而是要经过一层中转,可以看到在下面的代码中,我们调用的 greet 方法实际是从 hello_wasm.js 这个文件中导出的

<script type="module">
  import init, { greet } from './pkg/hello_wasm.js'
  init().then(() => {
    greet('WASM')
  })
</script>

实际的 greet 的代码

export function greet(name) {
  var ptr0 = passStringToWasm0(
    name,
    wasm.__wbindgen_malloc,
    wasm.__wbindgen_realloc
  )
  var len0 = WASM_VECTOR_LEN
  wasm.greet(ptr0, len0)
}

在调用 greet 时,会先将传入的 name 值写入 WASM 的内存中,最终传入给 WASM 的是内存的地址,而不是字符串

WASM 中如何调用 Web 中的方法

Rust 中的代码,借助wasm_bindgen通过 extern "C" 定义了要从外部引入的函数

#[wasm_bindgen]
extern "C" {
    // 在最终编译的结果中,alert 这一块会被替换
    pub fn alert(s: &str);
}

#[wasm_bindgen]
pub fn greet(name: &str) {
    alert(&format!("Hello, {}!", name));
}

在 WASM 初始化注入,流程可简化为下面

fetch('hello_wasm_bg.wasm').then(async (module) => {
  let imports = {}
  imports.alert = function (arg0, arg1) {
    // 获取到内存区域
    alert(getStringFromWasm0(arg0, arg1))
  }
  // 通过 imports 注入
  let wasm = await WebAssembly.instantiate(module, imports)

  // wasm 这个就是 WebAssembly 的实例了
  return wasm
})

以上通过共享同一块内存和注入函数的方式,实现了 JS 和 WASM 的双向通信

WASM 通信的性能消耗

修改了以下 Rust 中的代码,让其简单做一个字符串的拼接


#[wasm_bindgen]
pub fn greet(name: &str) -> String {
    let mut result: String = "prefix_".to_owned();
    result.push_str(name);

    return result;
}

同时在 HTML 中使用 JS 也实现一个版本

<script type="module">
  import init, { greet } from './pkg/hello_wasm.js'
  init().then((wasm) => {
    console.time('greetWASM')
    for (let i = 0; i < 20000; ++i) {
      greet('Test')
    }
    console.timeEnd('greetWASM')

    function greetJs(str) {
      return 'prefix_' + str
    }
    console.time('greetJS')
    for (let i = 0; i < 20000; ++i) {
      greetJs('Test')
    }
    console.timeEnd('greetJS')
  })
</script>

在 Chrome 87 版本下,运行的结果如下图

try-rust_ScreenShot2021-01-10at3.05.30PM

整体上来看相比于纯 JS 的执行,WASM 在数据传输上还是会消耗一定的时间,但整体的耗时相比于 Rust 在高计算复杂度场景下带来的提升影响不大,2 万次的调用耗时在 40ms 内

#tech