WebAssembly是Deno的好搭档

2020 年 8 月 24 日

WebAssembly是Deno的好搭档

本文要点


  • Deno和Node.js都在基于C/C++的运行时上执行JavaScript代码,以实现较高的性能。

  • Deno是单一的二进制应用,不兼容NPM模块,并且很难将原生模块加入应用中。

  • WebAssembly提供了一种在Deno应用中运行高性能代码的途径。

  • 对于服务端应用程序来说,WebAssembly是安全、可移植和轻量级的容器。

  • Rust编译器工具链为WebAssembly提供了强大的支持。


备受期待的Deno项目终于发布了1.0版本!Deno 是由 Node.js 的创始人 Ryan Dahl 创建的,旨在解决他所说的“我为Node.js感到遗憾的十件事”。


Deno 抛弃了 NPM 和臭名昭著的 node_modules。它是单个二进制可执行文件,可运行以 TypeScript 和 JavaScript 编写的应用程序。



但是,尽管 TypeScript 和 JavaScript 适合大多数 Web 应用程序,但它们可能难以满足计算密集型任务的需求,如神经网络训练和推理、机器学习和加密应用等。实际上,Node.js 就经常需要使用原生库来执行这些任务(例如使用 openssl 执行加密任务)。


既然没有类似 NPM 的系统来加入原生模块的话,我们该怎样在 Deno 上编写需要原生性能的服务端应用程序呢?这就要轮到 WebAssembly 上场了!在这篇文章中,我们将在 Rust 中编写一些高性能函数,并将它们编译为 WebAssembly,然后在你的 Deno 应用程序中运行它们。


太长不看版


从 GitHub 克隆或 fork这个Deno入门项目模板。按照说明操作,只需 5 分钟你就能在 Deno 中运行第一个 WebAssembly 函数(由 Rust 编写)。


一点背景


Node.js 之所以非常成功,是因为它为开发人员做到了鱼与熊掌兼得:JavaScript 的易用性(尤其是编写基于事件的异步应用程序时)以及 C/C++的高性能。Node.js 应用程序是用 JavaScript 编写的,但会在基于 C/C++的原生运行时上执行,这些运行时包括谷歌 V8 JavaScript 引擎和许多原生库模块。Deno 希望能复制这种成功路径,但在这个过程中它使用了 TypeScript 和 Rust 支持的现代技术栈。


Deno是用于JavaScript和TypeScript的简单、现代化且安全的运行时,它使用了V8引擎,并在Rust内构建。——deno.land网站。


在他的著名演讲“我为 Node.js 感到遗憾的十件事”中,Node.js 的创建者 Ryan Dahl 解释了从头开始创建 Deno 这个 Node.js 的竞争对手(甚至替代者)的理由。Dahl 的遗憾主要集中在 Node.js 管理第三方代码和模块的机制上。


  • 用于将C模块链接到Node.js的复杂构建系统。

  • package.json、node_modules、index.js和其他NPM工件引入了不必要的复杂性。


于是,Deno 在管理依赖项时有意选择了一些方式来避免上述问题。


  • Deno是单个二进制可执行文件。

  • 应用程序是使用TypeScript或JavaScript编写的,在代码中将依赖项明确声明为import语句,并带有完整的URL,链接到依赖项的源代码。

  • Deno与Node.js模块不兼容。


这些都没问题,但那些需要更高性能的应用程序该怎么办呢?例如需要在毫秒级别执行复杂神经网络模型运算的 AI 即服务应用程序?在 Deno 和 Node.js 中,许多函数都是通过 TypeScript 或 JavaScript API 调用,但以 Rust 或 C 语言编写的原生代码执行。在 Node.js 中,开发人员总是可以选择从 JavaScript API 调用第三方原生库。但我们目前无法在 Deno 中这样做吗?


Deno 中的 WebAssembly 支持


WebAssembly 是一种轻量级虚拟机,旨在以接近原生的速度执行可移植字节码。你可以将 Rust 或 C/C++函数编译为 WebAssembly 字节码,然后从 TypeScript 访问这些函数。对于某些任务,它可能比用 TypeScript 编写的等效函数要快得多。例如,这份IBM研究发现,对于某些数据处理算法,Rust 和 WebAssembly 可以将 Node.js 的执行速度提高 1200%至 1500%。


Deno 内部使用谷歌 V8 引擎。V8 不仅是一个 JavaScript 运行时,还是一个 WebAssembly 虚拟机。Deno 对 WebAssembly 提供了开箱即用的支持。Deno 为你的 TypeScript 应用程序提供了一个 API,以调用 WebAssembly 中的函数。


实际上,WebAssembly 中已经实现了一些流行的 Deno 组件。例如,Deno 中的sqlite module是使用 Emscripten 将 sqlite 的 C 源代码编译到 WebAssembly 中的成果。Deno WASI组件使 WebAssembly 应用程序可以访问操作系统的底层资源,例如文件系统。在本文中,我将教你如何用 Rust 和 WebAssembly 编写高性能的 Deno 应用程序。


设置


当然,第一步是安装Deno!在大多数系统上,这一步只需一条命令足矣。


$ curl -fsSL https://deno.land/x/install/install.sh | sh
复制代码


由于我们正在用 Rust 编写函数,因此你还需要安装Rust语言编译器和工具。


$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
复制代码


最后,ssvmup工具可以自动执行构建过程并生成所有工件,以使你的 Deno 应用程序轻松调用 Rust 函数。同样,用一条命令就能安装 ssvmup 依赖项。


$ curl https://raw.githubusercontent.com/second-state/ssvmup/master/installer/init.sh -sSf | sh
复制代码


注意:ssvmup 使用 wasm-bindgen 在 JavaScript 和 Rust 源代码之间自动生成“胶水”代码,以便它们可以使用自己的原生数据类型来通信。没有它,函数参数和返回值只能限制在 WebAssembly 原生支持的一些非常简单的类型上(如 32 位整数)。例如,如果没有 ssvmup 和 wasm-bindgen,你就无法使用字符串或数组。


Hello world


首先,我们来研究一下 Deno hello world 示例中使用的 hello world 示例。你可以从 GitHub 获取hello world源代码和应用程​​序模板


Rust 函数位于src/lib.rs文件中,只需在输入字符串前加上“hello”即可。注意,say()函数使用 #[wasm_bindgen]注解,使 ssvmup 可以生成必要的“管道”,以从 TypeScript 来调用它。


#[wasm_bindgen]pub fn say(s: &str) -> String {  let r = String::from("hello ");  return r + s;}
复制代码


Deno 应用程序位于deno/server.ts文件中。这个应用程序从 pkg/functions_lib.js 文件(由 ssvmup 工具生成)中导入 Rustsay()函数。functions_lib.js 这个文件名是由Cargo.toml文件中定义的 Rust 项目名称确定的。


import { serve } from "https://deno.land/std/http/server.ts";import { say } from '../pkg/functions_lib.js';
type Resp = { body: string;}
const s = serve({ port: 8000 });console.log("http://localhost:8000/");for await (const req of s) { let r = {} as Resp; r.body = say (" World\n"); req.respond(r);}
复制代码


现在我们运行 ssvmup,将 Rust 函数构建为一个 Deno WebAssembly 函数。


$ ssvmup build --target deno
复制代码


ssvmup 成功完成任务后,你可以检查 pkg/functions_lib.js 文件,看看 Deno WebAssembly API 是怎样执行已编译的 WebAssembly 文件 pkg/functions_lib.wasm 的。


接下来运行 Deno 应用程序。Deno 需要读取文件系统的权限(因为它需要加载 WebAssembly 文件),并需要访问网络(因为它需要接收和响应 HTTP 请求)。


$ deno run --allow-read --allow-net --allow-env --unstable deno/server.ts
复制代码


注意:如果你之前已经安装了 Deno,并在这里遇到了一个错误,这很可能是由于缓存过的库的版本冲突导致的。按照这里的指导来重建 Deno 缓存。


在另一个终端窗口中,你现在可以访问 Deno Web 应用程序,让它通过 HTTP 连接说 hello 了!


$ curl http://localhost:8000/hello  World
复制代码


一个更复杂的例子


入门模板项目包括了几个更详细的示例,以展示如何在 Deno TypeScript 和 Rust 函数之间传递复杂的数据。下面是 src/lib.rs 中的其他一些 Rust 函数。请注意,它们每个都用 #[wasm_bindgen]注解过了。


#[wasm_bindgen]pub fn obfusticate(s: String) -> String {  (&s).chars().map(|c| {    match c {      'A' ..= 'M' | 'a' ..= 'm' => ((c as u8) + 13) as char,      'N' ..= 'Z' | 'n' ..= 'z' => ((c as u8) - 13) as char,      _ => c    }  }).collect()}
#[wasm_bindgen]pub fn lowest_common_denominator(a: i32, b: i32) -> i32 { let r = lcm(a, b); return r;}
#[wasm_bindgen]pub fn sha3_digest(v: Vec<u8>) -> Vec<u8> { return Sha3_256::digest(&v).as_slice().to_vec();}
#[wasm_bindgen]pub fn keccak_digest(s: &[u8]) -> Vec<u8> { return Keccak256::digest(s).as_slice().to_vec();}
复制代码


也许最有趣的是 create_line()函数。它接收两个 JSON 字符串(每个字符串代表一个 Point 结构),并返回一个代表 Line 结构的 JSON 字符串。注意,Point 和 Line 结构都使用 Serialize 和 Deserialize 注解,这样 Rust 编译器就能自动生成必要的代码,以支持它们与 JSON 字符串之间的转换。


use wasm_bindgen::prelude::*;use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug)]struct Point { x: f32, y: f32}
#[derive(Serialize, Deserialize, Debug)]struct Line { points: Vec<Point>, valid: bool, length: f32, desc: String}
#[wasm_bindgen]pub fn create_line (p1: &str, p2: &str, desc: &str) -> String { let point1: Point = serde_json::from_str(p1).unwrap(); let point2: Point = serde_json::from_str(p2).unwrap(); let length = ((point1.x - point2.x) * (point1.x - point2.x) + (point1.y - point2.y) * (point1.y - point2.y)).sqrt();
let valid = if length == 0.0 { false } else { true }; let line = Line { points: vec![point1, point2], valid: valid, length: length, desc: desc.to_string() }; return serde_json::to_string(&line).unwrap();}
#[wasm_bindgen]pub fn say(s: &str) -> String { let r = String::from("hello "); return r + s;}
复制代码


接下来我们检查一下 JavaScript 程序deno/test.ts,它显示了如何调用 Rust 函数。如你所见,String 和 &str 是 JavaScript 的简单字符串,i32 是数字,而 Vec 或 &[8]是 JavaScript Uint8Array。JavaScript 对象需要先通过 JSON.stringify()或 JSON.parse()才能传入 Rust 函数或从 Rust 函数返回。


import { say, obfusticate, lowest_common_denominator, sha3_digest, keccak_digest, create_line } from '../pkg/functions_lib.js';
const encoder = new TextEncoder();
console.log( say("SSVM") );console.log( obfusticate("A quick brown fox jumps over the lazy dog") );console.log( lowest_common_denominator(123, 2) );console.log( sha3_digest(encoder.encode("This is an important message")) );console.log( keccak_digest(encoder.encode("This is an important message")) );
var p1 = {x:1.5, y:3.8};var p2 = {x:2.5, y:5.8};var line = JSON.parse(create_line(JSON.stringify(p1), JSON.stringify(p2), "A thin red line"));console.log( line );
复制代码


运行 ssvmup 构建 Rust 库之后,在 Deno 运行时中运行 deno/test.ts 会生成以下输出:


$ ssvmup build --target deno... Building the wasm file and JS shim file in pkg/ ...
$ deno run --allow-read --allow-env --unstable deno/test.tshello SSVMN dhvpx oebja sbk whzcf bire gur ynml qbt246Uint8Array(32) [ 87, 27, 231, 209, 189, 105, 251, 49, ... ...]Uint8Array(32) [ 126, 194, 241, 200, 151, 116, 227, ... ...]{ points: [ { x: 1.5, y: 3.8 }, { x: 2.5, y: 5.8 } ], valid: true, length: 2.2360682, desc: "A thin red line"}
复制代码


下一步计划


现在我们就可以创建 Rust 函数,并从 Deno TypeScript 应用程序访问它们。你可以在 Rust 函数中放置大量计算密集型任务,并通过 Deno 提供高性能和安全的 Web 服务。这类服务的例子包括机器学习和图像识别等。


将来,你还可以通过WebAssembly系统接口(WASI),在你的 Deno 应用程序中访问随机数、环境变量和文件系统等系统资源。


作者介绍


Michael Yuan 博士五本软件工程书籍的作者。他的最新著作《构建区块链应用》由 Addison-Wesley 在 2019 年 12 月出版。Yuan 博士是Second State的联合创始人,这是一家将 WebAssembly 和 Rust 技术引入云、区块链和 AI 应用程序的初创公司。Second State 使开发人员能够在 Node.js 上部署快速、安全、可移植和无服务器的 Rust 函数。感兴趣的读者可以订阅WebAssembly.Today通讯来获取最新信息。


原文链接:


Deno Loves WebAssembly


2020 年 8 月 24 日 14:411297

评论 4 条评论

发布
用户头像
重新发明J A V A(滑稽
2020 年 08 月 25 日 10:16
回复
技术的发展是螺旋式上升😂 历史总是惊人的相似,却又不会简单重复。
2020 年 08 月 25 日 16:25
回复
用户头像
既然在服务端了,直接运行python或者c++不就行了。为何还要转成webAssembly 再运行。
2020 年 08 月 24 日 16:10
回复
用 WebAssembly 比 Python 快,比 C++ 安全与跨平台,更多可以阅读这篇文章:https://www.secondstate.io/articles/why-webassembly-server/
2020 年 08 月 25 日 01:43
回复
没有更多评论了
发现更多内容

第三周总结

Geek_ac4080

开源的意义与价值

Braisdom

Java 开源 ORM

第四周

Geek_fabd84

usdt承兑商支付系统开发源码,区块链支付搭建

WX13823153201

架构师训练营第三周:系统架构

m

各角色如何从DevOps中受益?

DevOps 产品经理 测试 开发 运维工程师

JavaScript 语言通识 — 重学 JavaScript

三钻

Java 前端进阶

架构师训练营1期第三周作业

木头发芽

Nginx 整合 FastDFS 实现文件服务器

哈喽沃德先生

nginx 文件系统 分布式文件存储 fastdfs 文件服务器

节日快乐…吗?

小天同学

个人感悟 国庆中秋 假期 节日

MySQL-技术专题-问题分析

李浩宇/Alex

基于区块链技术实现“资产通证化”

CECBC区块链专委会

资产证券化 流动性

手把手教你锤面试官 03——Spring怎么那么简单

慵懒的土拨鼠

如何使用 dotTrace 来诊断 netcore 应用的性能问题

newbe36524

微服务 .net core netcore ASP.NET Core

架构师训练营第一期 - 第四周课后 - 作业一

极客大学架构师训练营

看动画学算法之:linkedList

程序那些事

数据结构和算法 看动画学算法 看动画学数据结构 算法和数据结构

Python时间序列分析简介(1)

计算机与AI

Python pandas 数据处理 时间序列

~~寒露节记~~

wo是一棵草

关于代码审查的一点体会

KJ Meng

敏捷开发 研发管理 代码审查 Code Review

字节跳动 Flink 单点恢复功能实践

Apache Flink

flink

架构师训练营 第三周作业

haha

极客大学架构师训练营

MySQL-技术专题-主从复制原理

李浩宇/Alex

区块链技术最重要价值所在

CECBC区块链专委会

区块链 数字经济 经济

线上服务平均响应时间太长,怎么排查?

小Q

Java 程序员 测试 Jmeter 性能调优

「剑指offer」27道Mybatis面试题含解析

Java架构师迁哥

第三周作业

Geek_ac4080

私有云PAAS平台的思考

8小时

这可能是GitHub上最适合计算机专业学生看的编程教程

小Q

Java 学习 编程 面试 基础

如果朋友圈没有点赞功能,你还会发朋友圈吗

彭宏豪95

微信 产品 互联网 写作

国庆期间,我造了台计算机

yes的练级攻略

计算机 底层

云原生虚机应用托管-设计篇

8小时

WebAssembly是Deno的好搭档-InfoQ