webassembly
- webassembly学习
- 基本理论
- webassembly介绍
- wasm介绍
- 基本信息
- wasm会替换javascript么
- ASM.js(wasm的前身)
- 将 WebAssembly 作为编程语言的一种尝试
- wasm应用场景
- wasm运行原理
- 周边生态
- WASI 操作系统接口
- wasi介绍
- wasm+wasi(服务端)
- wasm runtime对比(虚拟机)
- wasmedge
- wasmtime
- wasmer
- wamr
- wavm
- emscripten教程
- TODO
- 模型推理
- paddlejs
- paddlejs小程序跑模型
- MNN.js
- NCNN
- tfjs
- openvinojs
- onnxruntimejs
- wasm+wasi替代docker,服务端
- webgl
- 部分功能实现
- SQL
webassembly学习
基本理论
webassembly介绍
产生的原因:希望将一些非javascript的代码运行在浏览器上,避免重新开发的工作量。
webassembly诞生
wasm介绍
系统学习webassembly
webassembly实现细节
为什么webassembly是web的未来?
基本信息
- wasm 是一个可移植、体积小、加载快并且兼容 Web 的全新格式.
- wasm 代码格式: - .wasm - wasm二进制格式
- .wabt - web assembly text format,编译结果的文本格式,用于调试
- 通过wabt可以实现wasm和wabt格式的转换
wasm会替换javascript么
wasm不会替换javascript,可以帮助javascript解决一些无法解决的问题
- 解除对不同浏览器环境的强依赖
如今现代前端开发对webpack、babel、polyfill的依赖都非常重,编译后动辄就是几兆几兆的大小,当一个业务非常复杂之后,不得不采用各种各样的手段来做优化。另外,浏览器的安全也是一个大问题。
- 弱类型还是强类型
尽管我们有TypeScript,然而它只能在编译阶段帮我们做好类型的校验,除此之外,如 + 运算符、null == undefined 等诡异结果也是JavaScript种种难以克服的通病。
- WASM 的设计初衷就可以梳理为以下几点:
1)最大程度的复用现有的底层语言生态,如 C/C++ 在游戏开发、编译器设计等方面的积淀
2)在 Web、Node.js 或其他 WASM runtime 获得近乎于原生的性能,也就是可以让浏览器也能跑大型游戏、图像剪辑等应用
3)还有最大程度的兼容 Web、保证安全
4)同时在开发上(如果需要开发)易于读写和可调试,这一点 AssemblyScript 走得更远
所以从初衷出发,WebAssembly 的作用更适合下面这张图:
WASM 桥接各种系统编程语言的生态,进一步补齐了 Web 开发生态之外,还为 JS 提供性能的补充,正是 Web 发展至今所缺失的重要的一块版图。
一种直接运行在web前端,一种运行在后端服务,通过wasm runtime来启动
ASM.js(wasm的前身)
ASM.js仍然是被编译出来的,书写的C/C++代码经过静态编译后,会输出为一段特定的JavaScript代码,浏览器引擎在运行这段代码时,会做特别的优化。
阮一峰的入门
javascript是动态语言,当多次调用同一个函数,参数类型发生变化后,需要重新进行parser-compile。而Asm.js是静态编译语言,会更快,去除了Javascript垃圾回收机制。详细可看:
为什么webassembly是web的未来?
将 WebAssembly 作为编程语言的一种尝试
因为上述的二进制和文本格式都不适合编码,所以不适合将 WASM 作为一门可正常开发的语言。
为了突破这个限制,AssemblyScript[6] 走到台前,AssemblyScript 是 TypeScript 的一种变体,为 JavaScript 添加了 WebAssembly 类型[7] , 可以使用 Binaryen[8] 将其编译成 WebAssembly。
目前使用 AssemblyScript 构建的应用生态已经初具繁荣,目前在区块链、构建工具、编辑器、模拟器、游戏、图形编辑工具、库、IoT、测试工具等方面都有大量使用 AssemblyScript 构建的产物:https://www.assemblyscript.org/built-with-assemblyscript.html#games
wasm应用场景
wasm运行原理
- 如何运行的
一般来说,一个可运行的程序分为两部分,即数据 + 指令,而WASM是基于堆栈机模型和虚拟指令集来实现程序的运行,堆栈机模型是常见的内存管理结构,将数据和操作符压入栈中并弹出逐次执行,而虚拟指令集(V-ISA)则可以认为是对平台无关的一系列自定义操作符(如JVM),相对的,物理指令集(ISA)则是强依赖物理系统的(如Intel的x86-64)。
我们把浏览器比作JVM虚拟机,WASM二进制编码比作Java字节码,是不是就能很快明白为什么浏览器上可以运行WASM了? - 剖析WASM文件结构
首先在组织结构上,WASM会把特定功能或者有相关联的代码放进一个特定的区块(Section)中,而这些区块(Section)组成了程序。
1)TypeSection
存放与“类型”相关的内容,主要是“函数签名”,即返回值与参数值。
2)StartSection
在模块初始化完成后,被首先调用的函数,可以近似理解为main函数
3)GlobalSection
顾名思义,存放了程序相关的全局变量,可能是程序自定义的数据,也可能是流程相关的
4)CustomSection
可以用作将来自定义扩展功能的实现
5)ImportSection
从宿主环境中,我们可以向WASM模块导入各式各样的数据,换句话说,通过这个Section我们可以实现数据或代码的共享
6)ExportSection
同理,既然可以导入,我们也可以导出数据和方法
7)FunctionSection
这里存储的是函数类型,与TypeSection一一对应
8)CodeSection
这里存储的是函数具体代码,与FunctionSection一一对应
9)TableSection、ElementSection
Table存放了函数指针的元信息,Element则是对应Table的具体内容,关于Table的概念可以参考这篇文章:https://zhuanlan.zhihu.com/p/28222049
10)MemorySection、DataSection
同上,Memory描述了使用内存的基本情况,Data则是对应具体的实际内容
wasm以 asm这个字符串的二进制编码开头,随后跟上版本号。 - 基本数据类型
1)无符号整数
WASM支持三种非负整数类型: uint8、uint16、uint32,后面的数字表示占用了多少个bit
2)可变长无符号整数
WASM支持三种可变长非负整数类型: varuint1, varuint7, varuint32,所谓可变长的意思是会根据具体数据大小决定使用多少bit,后面的数字表示最大可占用多少个bit
3)可变长有符号整数
同上,这里允许负数的出现,这里支持varint7, varint32, varint64三种类型
4)浮点数
这里同JS,采用IEEE-754方案,单精度为32位
周边生态
-
编译工具
Emscripten、LLVM、Binaryen -
WAT 使WASM更加可读
通常来说,WASM二进制文件是不可读的,WebAssembly Text Format(WAT)是另外一种输出格式,以类文本的方式展示输出,我们可以近似的理解为与二进制等价的汇编语言,或者说就是WASM的source-map。
另外,Flat-WAT是经过优化后的WAT格式,我们可以一张图比较三者输出:
现在社区已经有成熟的转换工具,如wasm2wat,wat2wasm等,我们可以在一个名为WABT(WebAssembly Binary Toolkit)的工具集中找到。
WASI 操作系统接口
wasi介绍
WASI是一个新的API体系, 由Wasmtime项目设计, 目的是为WASM设计一套引擎无关(engine-indepent), 面向非Web系统(non-Web system-oriented)的API标准. 目前, WASI核心API(WASI Core)在做覆盖文件,网络等等模块的API, 但这些实现都是刚刚开始实现, 离实用还是有很长路要走。
本质让wasm可以在非web系统上运行,把Wasm模块放进Runtime提供的沙箱中运行,Runtime可以限制模块的对于操作系统的访问,为其提供有限的文件系统访问等。而Runtime可以被设计成跨平台的,我们可以在Linux/Windows/MacOS/嵌入式等平台上运行同一个Wasm模块。这就相当于我们有了跨平台且安全的二进制执行方法。
wasm+wasi(服务端)
运行在服务端,类似docker
场景:对于需要频繁启动和停止的场景,会更有优势,服务端docker一旦启动,资源就会一直占用,对于需要高速响应,但是请求却不是很持续。
wasm云原生
wasm runtime对比(虚拟机)
参考资料:
四种主流wasm
对比
wasmedge
wasmedgr
wasmtime
WASMTIME是字节码联盟主推的一个WASM虚拟机,既可以作为一个CLI,也可以被嵌入到其他应用系统中,如IoT或者云原生
wasmtime编译cpython
wasmtime
wasmer
这是独立于字节码联盟,并努力构建自己生态的社区推出的产品,特点是支持在更多的编程语言运行WASM实例,并有自己的包管理平台Wapm
wasmer
wamr
同样是字节码联盟旗下的,更偏向于芯片场景的虚拟机,如它的名字所示,体积非常小,起步速度只要100微秒,内存耗费最低只需100KB
wamr
wavm
wavm
WAVM是WebAssembly虚拟机,设计用于非Web应用程序。
特点
- 快速:WAVM使用LLVM将WebAssembly代码编译为具有接近本机性能的机器代码。在某些情况下,它甚至可以胜过本机性能,这要归功于它能够生成针对运行代码的确切CPU进行了调整的机器代码。
- 安全:WAVM阻止WebAssembly代码访问WebAssembly虚拟机*之外的状态,或调用未与WebAssembly模块明确链接的本机代码。
https://github.com/WAVM/WAVM
wavm run helloworld.wast
wavm run zlib.wasm
wavm run trap.wast
wavm run echo.wast "Hello, world!"
wavm run helloworld.wast | wavm run tee.wast
wavm run --enable simd blake2b.wast
emscripten教程
参考资料:
emscripten中文
emscriten英文
TODO
模型推理
paddlejs
paddlejs垃圾分类
paddlejs
paddlejs小程序跑模型
- 第一步:准备一个paddlepaddle模型
模型使用的是链接中的模型:
导出的paddle模型 - 第二步:模型转换
pip install paddlejsconverter
paddlejsconverter --modelPath=./model.pdmodel --paramPath=./model.pdiparams --outputDir=./ --useGPUOpt=True
paddle导出的模型转换后,会报错,issue有人提出,等待解决:
webgl createProgram: conv2d -- Error: Error: compile: ERROR: 0:186: 'undefined' : undeclared identifier
ERROR: 0:186: '' : boolean expression expected
先使用:
转换好的模型
- 第三步:准备微信小程序环境
工程可以直接拉大佬的代码:小程序工程。以下代码只是重要部分,不全。
1)下载微信小程序开发工具:
微信小程序
2)新建项目, appID需要注册,测试号使用不是特别方便,需要用申请到的appid创建项目,且选择不使用云开发 。
微信小程序账号申请
如果新建项目时用的游客,可通过微信小程序开发工具(详情–>基本信息修改appid)
3)添加paddlejs插件。位置:先登录微信公众号平台,设置–>第三方设置–>插件管理–>添加插件,搜索:wx7138a7bb793608c3.
添加这个插件是为了给appid账号授权paddlejs。
4)在小程序的app.json 中声明插件的provider(appid)& version(指定版本号)
{
...
"plugins": {
"paddlejs-plugin": {
"version": "2.0.1",
"provider": "wx7138a7bb793608c3"
}
}
...
}
5)进入小程序项目根目录(可以通过右键,通过资源管理器打开进入或者知道文件创建的路径)
6)使用npm init -yes 创建初始化,此时会出现一个package.json(如果没有npm需要去安装nodejs)。
7)使用npm包引入@paddlejs/paddlejs-core与@paddlejs/paddlejs-backend-webgl,具体如下
在package.json中添加或修改如下
{
…
“dependencies”: {
“@paddlejs/paddlejs-backend-webgl”: “^1.0.7”,
“@paddlejs/paddlejs-core”: “^2.0.7”
}
}
回到小程序根目录,执行npm i。
8)使用小程序开发者工具,点击 工具-》构建npm!!!
同时关闭合法域名校验(右边详情 -》 本地设置 -》 不校验合法域名…)!!!
到这一步,基本上环境是搭建好了,准备使用
9)在app.js或在小程序page的js文件中引入 @paddlejs/paddlejs-core、@paddlejs/paddlejs-backend-webgl 并初始化
import * as paddlejs from '@paddlejs/paddlejs-core';
import '@paddlejs/paddlejs-backend-webgl';
const plugin = requirePlugin("paddlejs-plugin");
plugin.register(paddlejs, wx);
export const PaddleJS = new paddlejs.Runner({
modelPath: 'http://127.0.0.1', //你本地的ip地址也可以用
feedShape: {
fw: 224,
fh: 224
},
fill: '#fff',
targetSize: {
height: 224,
width: 224
},
bgr: false,
mean: [0.485, 0.456, 0.406],
std: [0.229, 0.224, 0.225],
needPreheat: true,
webglFeedProcess: true
});
10)现在拿出你转换后的模型,进入目录,使用python -m http.server 80进行挂载,这样可以通过127.0.0.1进行访问 这也是为什么modelPath填写127.0.0.1的原因,你可以测试一下,现在是否能通过127.0.0.1/model.json访问model.json
11)如果初始化成功,那么就剩下预测的代码了。
test: function(){
wx.chooseImage({
count: 1,
sizeType: ['original'],
sourceType: ['album', 'camera'],
success:res => {
console.log(res)
this.setData({
imageSrc: res.tempFilePaths[0]
})
wx.getImageInfo({
src: res.tempFilePaths[0],
success: res=> {
let {width, height, path} = res;
const ctx = wx.createCanvasContext("myCanvas")
console.log(ctx)
ctx.drawImage(path, 0, 0, width, height);
ctx.draw(false, () => {
wx.canvasGetImageData({
canvasId: "myCanvas",
height: height,
width: width,
x: 0,
y: 0,
success: res => {
PaddleJS.predict(res).then(res =>{
console.log(res)
const max = Math.max.apply(null, res);
const index = res.indexOf(max);
console.log(index, max)
console.log(labelMap[index])
})
},
fail: res => {
console.log(res)
}
},)
})
}
})
}
})
},
12)最后编译,可以在模拟器中查看和调用。
如果要更换模型,需要先清理缓存(全部清理),然后重新编译。
现在存在两个问题:
1、很多paddlepaddle模型转换会报错(错误就没有具体深究)
2、速度比较慢(facedetect的demo测试约4-5秒,垃圾分类模型约180ms)。
MNN.js
MNN.js
NCNN
tfjs
openvinojs
onnxruntimejs
wasm+wasi替代docker,服务端
webgl
部分功能实现
SQL
SQL