WebAssembly学习记录

1.WebAssembly

1.1 指令集

概念:二进制编码集合。

依据计算机组成原理和计算机概论,指令集是一组二进制编码。

作用:控制硬件。

这些二进制指令直接作用于硬件电路,控制硬件完成指定操作。

  • 例如:控制数据进入某个寄存器,控制数据做位运算。

分类: 按CPU分类。

不同架构的CPU有不同的指令集。

  • 例如:x86和arm架构CPU指令集不同。

1.2 WebAssembly

概念:低级静态程序语言。

WebAssembly可以看做是针对于浏览器的一组指令集。它是低级程序语言。

  • 例如:它和汇编语言类似,只不过不同CPU的汇编语言不同,但是不同浏览器的WebAssembly都相同。

作用:在浏览器中运行。

除了JavaScript外另一种可以直接在浏览器中运行的语言。

  • 例如:因为WebAssembly可以在浏览器中运行,因此C/C++,Rust等语言选择把它们编译成WebAssembly在浏览器运行。

特点:效率高。

不需要通过高级程序员的解释器或编译器,是编译产物,免去这个耗时操作。

  • 例如:高级程序语言的一系列操作可以被汇编级语言优化成几次简单操作,例如优化后简单将数据在寄存器间移动就可能完成多个语句表示的操作。

2.优化密集计算型业务

2.1 编译流程概述

流程背景

编译C/C++指定函数到wasm文件,为JavaScript提供计算密集型任务的高效解决方案。

流程概述

按照emscripten官方文档安装编译器并对C/C++打包。

注:也可以编译整个C/C++文件到wasm,本流程只导出函数

  • 激活环境: 运行 emsdk_env.batemsdk_env.sh激活编译器环境

  • 编译: 运行 emcc test1.cpp -s EXPORTED_FUNCTIONS=_fib,_add -O3 -o fib-emcc.wasm编译成wasm文件

命令说明

注:官网上提到过导出cwrap,ccall来给JavaScript调用导出的C/C++函数。但是安装最新版emscripten发现没用。

  • -s EXPORTED_FUNCTIONS= 指明导出的C/C++函数,函数名前需要加下划线
  • -O 指明编译优化方案,和g++中 -O指令含义相同

2.2 编译流程详解

2.2.1 C/C++部分

extern "C"连接指示符:

按C语言规则来处理函数名称。Emscripten要求导出C++函数时必须有该操作。

main函数:

Emscripten要求导出C/C++函数时,即使不导出main函数也要必须写main函数。

开辟内存和写入内存函数:

JavaScript传入引用类型给Wasm模块时直接传入会失败。因为C++中引用类型需要指明指针地址,如果JavaScript需要传入引用类型,需要在Wasm模块中开辟一块空间写入传入的参数,这样C++才能访问到。如果传入普通类型不需要考虑开辟空间。

#include<algorithm>

extern "C" {
	// 将导出给JavaScript:a+b函数
	int add(int a, int b) {
		return a + b;
	}

	// 将导出给JavaScript:开辟内存空间函数
	int* createIntArray(int length) {
		return new int[length];
	}
	// 将导出给JavaScript:写入内存空间函数
	void writeToArray(int* arr, int index, int value) {
		arr[index] = value;
	} 

	// 将导出给JavaScript:快排函数
	int* qSort(int* arr, int length) {		
	    std::sort(arr, arr + length);
	    return arr;
	}

	// 将导出给JavaScript:斐波那契数列函数
	int fib (int n) {
	  if (n <= 0) return 0;
	  if (n <= 2) return 1;
	  return fib(n - 2) + fib(n - 1);
	}
}


int main () {}
2.2.2 JavaScript部分

读取WebAssembly文件:

读取WebAssembly文件方法有很多,经过若干版本迭代,使用MDN最新推荐的方法 WebAssembly.instantiateStreaming(fetch(fileName), importObject)读取

传入C/C++模块初始值:

类似node运行时可以传参 node build.js --env=development一样,C/C++编译后的wasm模块也需要传参。传参的标准可以参考WASI(专门为WebAssembly设计的)规范。传参通过读取wasm模块的第二个参数传递,是否传参由wasm模块决定。

由下图圈出部分可知需要给wasm模块传递什么样的参数。

注:直接打开wasm文件打不开,都是二进制。可以通过浏览器查看对应的浏览器层面的汇编语言。

注:下面的传参很简单。如果在C/C++引入了更多的模块,例如 #incldue<iostream>需要传入更多初始参数。

在这里插入图片描述

传递引用到wasm模块:

下述代码在调用wasm的 qSort时不能直接把一个JavaScript数组传递过去,这样会导致C/C++的快排函数读取失败。传递引用时需要有C/C++编程思想:

  • 传递指针: 首先肯定要传递指针,指针的值实际是地址,所以需要传数值。
  • 申请空间: 需要在wasm模块中为函数形参申请内存空间,并将需要排序的数组写入。

读取引用从wasm模块:

读取引用类型和传递引用类型类似,都要以指针为中心。

  • 读取指针: qSort函数返回指针,其实就是数值,需要我们从wasm模块的内存中读取对应地址内容。
  • 转换为JavaScript对象: 转换为JavaScript对象一般利用 Buffer类实现,下面文件接收 int数组,因此使用 Int32Array类转化为JavaScript对象。
<html>
  <head>
    <meta charset="utf-8" />
  </head>
  <body>
    <script>
      // 初始化随机数据
      const handleInitData = (length) => {
        return Array.from({ length }).map(() =>
          Math.floor(Math.random() * length)
        );
      };

      // 加载WebAssembly文件
      const handleUseWebAssembly = (fileName) => {
        // C++运行时传入的参数(具体传入什么变量需要等wasm生成后再分析。)
        const importObject = {
          wasi_snapshot_preview1: {
            proc_exit: function (code) {
              console.log(`Process exited with code ${code}`);
            },
          },
        };
        // 利用fetch加载wasm文件
        return WebAssembly.instantiateStreaming(fetch(fileName), importObject);
      };

      // 把数据传给wasm模块
      const handlePostData = (result, initData) => {
        // 获取C++提供的函数
        const {
          // 在C++所在的wasm中申请数组内存
          createIntArray,
          // 在C++所在的wasm中向某个内存地址写入数据
          writeToArray,
        } = result?.instance?.exports;

        const length = initData.length;

        // 在wasm申请一片内存空间,用于存储数组
        const arrPointer = createIntArray(length);
        // 向刚刚申请的内存空间中写入数据
        Array.from({ length }).forEach((_, index) =>
          writeToArray(arrPointer, index, initData[index])
        );

        return arrPointer;
      };

      // 排序
      const handleSort = (result, initData, dataPointer) => {
        const length = initData.length;

        const startJS = performance.now();
        // sort默认转成字符串排序,需要转回数值再排序
        initData.sort((a, b) => Number(a) - Number(b));
        const endJS = performance.now();
        console.log(`JS排序${length}条数据用时${(endJS - startJS) / 1000}`);

        const { qSort } = result?.instance?.exports;
        const startC = performance.now();
        const newPointer = qSort(dataPointer, length);
        const endC = performance.now();
        console.log(`C++排序${length}条数据用时${(endC - startC) / 1000}`);

        return newPointer;
      };

      // 输出排序结果
      const handlePrint = (result, length, pointer, jsResult) => {
        const data = new Int32Array(
          // Buffer对象,表示获取WebAssembly文件使用的内存空间
          result.instance.exports.memory.buffer,
          pointer,
          length
        );
        const cResult = Array.from(data);

        console.log("JavaScript排序结果是", jsResult);
        console.log("C++排序结果是", cResult);
      };

      (async () => {
        // 初始化10万条随机数用于排序
        const length = 1e6;
        // 要导入的WebAssembly文件
        const fileName = "./test-emcc.wasm";

        // 初始化数据
        const initData = handleInitData(length);
        // 读取WebAssembly文件
        const result = await handleUseWebAssembly(fileName);
        // 把初始化数据传给WebAssembly文件
        const dataPointer = handlePostData(result, initData);
        // 排序
        const newPointer = handleSort(result, initData, dataPointer);
        // 输出排序结果
        handlePrint(result, initData.length, newPointer, initData);
      })();
    </script>
  </body>
</html>
2.2.3 Web服务器部分

上面直接引入wasm文件会有同源限制问题。下面用原生node实现一个简单的web服务器解决该问题。

let fs = require("fs");
function read(path, res) {
fs.readFile(path, function (err, data) {
 if (err) console.log(err);
 else if (path.slice(-2, path.length) === "js") {
   res.writeHead(200, {
     "Content-Type": "text/javascript",
   });
   res.write(data);
   res.end();
 } else if (path.slice(-4, path.length) === "html") {
   res.writeHead(200, {
     "Content-Type": "text/html",
   });
   res.write(data);
   res.end();
 } else if (path.slice(-4, path.length) === "wasm") {
   res.writeHead(200, {
     "Content-Type": "application/wasm",
   });
   res.write(data);
   res.end();
 }
});
}
require("http")
.createServer(function (req, res) {
 if (req.url === "/favicon.ico") res.end();
 else read("." + req.url, res);
})
.listen(3000, function (err) {
 if (err) console.log(err);
 else console.log("运行成功");
});

2.3 计算结果

实验结果阐述

输出结果表示wasm模块执行效率要比JavaScript效率高了一倍以上,但是这只是一个简单的使用。网上有其它实验表示平均基于C/C++的wasm模块的效率要比JavaScript高30%~50%,但wasm相比C/C++直接编译后再生成exe文件在windows上运行效率低30%。

在这里插入图片描述

实验结果概述

运行速度:JavaScript > 基于C/C++的wasm模块 > 原生环境的C/C++

2.4 特点分析

效率高

虽然没有让性能成倍提升,但是提升30%~50%的性能还可以让人接受。

编译过程参考少

C/C++编译成wasm时发现Emscripten的文档很老,并且网上参考博客也很古老,参考内容不多,出错不好排查。

需要前端直面指针和内存

相互交换引用类型时,使用JavaScript不得不直面指针和内存操作。和JavaScript设计初衷不符。

3.优化Tensorflow

3.1 优化流程概述

流程背景

为了缓解服务器压力,需要将某些模块的人脸识别功能迁移到前端完成。目前使用了基于tensorflow的 face-api相关库和模型实现,考虑使用多线程和wasm做进一步优化。

流程概述

还没有分析和实现完,暂时用 face-api的测试仓库举例。

注意事项

  • 跨环境对比: face-api中只提供了node环境的wasm模块,对于浏览器环境没有给出wasm模块。因此只用node下的wasm模块和浏览器的普通模块做对比。
  • 不在node环境下对比: 不用node环境作对比是因为需要安装的包太麻烦,里面的tensorflow并不是全部由JavaScript实现,还需要安装精确版本的python和C/C++的编译器。
  • 对比存在差异: face-api提供的node下的wasm版本并不支持使用GPU加速,但是浏览器环境下支持自动使用GPU加速。因此对比时会考虑到GPU加速带来的影响。

3.2 优化流程详解

3.2.1 浏览器部分

基于demo中的index.js进行修改(下面只是截取了修改的部分)。

注:计算时间时考虑YOLO目标检测和face-api人脸识别两个神经网络的时间。

for (const img of samples) {
 document.body.appendChild(document.createElement("br"));
 const canvas = await image(img);
 try {
     const start = performance.now();
     const dataTinyYolo = await faceapi
     .detectAllFaces(canvas, optionsTinyFace)
     .withFaceLandmarks()
     .withFaceExpressions()
     .withFaceDescriptors()
     .withAgeAndGender();

     const dataSSDMobileNet = await faceapi
     .detectAllFaces(canvas, optionsSSDMobileNet)
     .withFaceLandmarks()
     .withFaceExpressions()
     .withFaceDescriptors()
     .withAgeAndGender();
     const end = performance.now();
     const time = (end - start) / 1000;
     timeList.push(time);
     console.log(`图片${timeList.length}耗时${time}`);
 } catch (err) {
     log(`Image: ${img} Error during processing ${str(err)}`);
 }
}

console.log(
 `平均耗时${timeList.reduce((sum, time) => time + sum, 0) / timeList.length}`
);
3.2.2 Node部分

基于demo中的node-wasm.js进行修改。修改内容较多,下述代码给出了详细的注释。

注:计算时间时由于没有YOLO目标检测,这里只计算face-api识别人脸的时间。

const fs = require("fs");
const image = require("@canvas/image");
const tf = require("@tensorflow/tfjs");
const wasm = require("@tensorflow/tfjs-backend-wasm");
const faceapi = require("../dist/face-api.node-wasm.js");

async function readImage(imageFile) {
const buffer = fs.readFileSync(imageFile);
const canvas = await image.imageFromBuffer(buffer);
const imageData = image.getImageData(canvas);

// 图片转化成tensor向量
const tensor = tf.tidy(() => {
 const data = tf.tensor(
   Array.from(imageData?.data || []),
   [canvas.height, canvas.width, 4],
   "int32"
 );
 const channels = tf.split(data, 4, 2);
 const rgb = tf.stack([channels[0], channels[1], channels[2]], 2);
 const squeeze = tf.squeeze(rgb);
 return squeeze;
});
return tensor;
}

async function main() {
// 加载wasm形式的tensorflow
wasm.setWasmPaths(
 // 国内jsdelivr镜像
 "https://jsd.cdn.zzko.cn/npm/@tensorflow/tfjs-backend-wasm/dist/",
 true
);
await tf.setBackend("wasm");
await tf.ready();

console.log(`FaceAPI版本 ${faceapi.version}`);
console.log(`TensorFlow版本 ${tf.version_core}`);
console.log(`TensorFlow模式 ${faceapi.tf.getBackend()}`);

// 加载SSD Mobilenet V1神经网络判断所有面部
await faceapi.nets.ssdMobilenetv1.loadFromDisk("model");
// 加载68点阵判断面部神经网络
await faceapi.nets.faceLandmark68Net.loadFromDisk("model");
// 加载年龄和性别神经网络
await faceapi.nets.ageGenderNet.loadFromDisk("model");
// 加载人的脸判断神经网络
await faceapi.nets.faceRecognitionNet.loadFromDisk("model");
// 加载标签神经网络
await faceapi.nets.faceExpressionNet.loadFromDisk("model");

// 配置第一个网络SSD神经网络网络的参数(其它神经网络用默认参数)
const options = new faceapi.SsdMobilenetv1Options({
 minConfidence: 0.1,
 maxResults: 10,
});

// 待检测图片列表
const ImageList = Array.from({ length: 6 });
const getImagePath = (index) => `demo/sample${index}.jpg`;

// 依次检测图片(测试发现下面faceapi并行调用会阻塞)
const detectList = ImageList.reduce(async (previousPromise, _, index) => {
 const timeList = await previousPromise;

 const tensor = await readImage(getImagePath(index + 1));
 const start = performance.now();

 // 调用神经网络进行识别
 await faceapi
   .detectAllFaces(tensor, options)
   .withFaceLandmarks()
   .withFaceExpressions()
   .withFaceDescriptors()
   .withAgeAndGender();

 const end = performance.now();
 const elapsedTime = (end - start) / 1000;
 timeList.push(elapsedTime);
 console.log(`图片${index + 1}耗时${elapsedTime}`);

 // 销毁向量
 tf.dispose(tensor);

 return Promise.resolve(timeList);
}, Promise.resolve([]));

detectList.then((list) => {
 const averageTime = list.reduce((sum, time) => sum + time, 0) / list.length;
 console.log(`平均时间是${averageTime}`);
});
}

main();

3.3 优化结果

结论

不使用GPU加速的tensorflow的wasm版本的速度逼近使用GPU加速的tensorflow的普通JavaScript版本。可以看出wasm还是有明显加速效果的。

浏览器部分

使用GPU加速tensorflow。去除第一个极端例子后,最终平均时间在1秒以内,集成显卡使用率有明显波动。

在这里插入图片描述

Node部分

使用wasm加速tensorflow。最终平均时间是1秒出头,两个GPU的使用率没有明显波动

在这里插入图片描述

4.优化多线程

4.1 优化举例

哔哩哔哩的投稿页面可以看到有两个运用wasm的地方,第一个是直接提供计算密集型的操作函数。第二个是工作线程,可以看出是在tensorflow的工作线程中又引入了wasm提速,主要是用于快速生成AI视频封面。

在这里插入图片描述

4.2 优化线程池举例

例子后续再写

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/574957.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何通过质构分析仪客观评价面包的硬度、咀嚼性等口感指标

如何通过质构分析仪客观评价面包的硬度、咀嚼性等口感指标 一、引言&#xff1a;面包口感品质的重要性 面包作为日常生活中常见的食品之一&#xff0c;其口感品质直接影响到消费者的购买决策和食用体验。其中&#xff0c;硬度和咀嚼性是衡量面包口感品质的重要指标。因此&…

车企如何利用数据技术,指导汽车全生命周期的业务运营?

引言&#xff1a;数据正作为重点&#xff0c;为行业提供不可或缺的指导 《汽车数据发展研究报告&#xff08;2023&#xff09;》指出&#xff0c;汽车行业正由传统硬件制造向“电动化、智能化、网联化”方向转变。德勤预测&#xff0c;到 2025 年&#xff0c;汽车行业 20%的利…

小程序AI智能名片S2B2C商城系统:四大主流商业模式深度解析与实战案例分享

在私域电商迅速崛起的大背景下&#xff0c;小程序AI智能名片S2B2C商城系统以其独特的商业模式和强大的功能&#xff0c;正成为品牌商们争相探索的新领域。在这一系统中&#xff0c;拼团模式、会员电商、社区团购和KOC营销等四种主流模式&#xff0c;为品牌商提供了多样化的营销…

深度探讨容器化技术在网络安全中的应用与挑战

随着容器化技术的快速发展&#xff0c;尤其是Docker与Kubernetes&#xff08;K8s&#xff09;的广泛应用&#xff0c;企业IT架构正经历着从传统虚拟机向轻量级容器的深刻变革。容器化技术为提升资源利用率、加速应用部署及维护提供了强大支持&#xff0c;但同时也给网络安全带来…

bugfix: com.alibaba.druid.sql.parser.EOFParserException: EOF

前言 在日常的开发工作中&#xff0c;我们经常会遇到各种各样的问题&#xff0c;其中涉及数据库操作的接口联调尤其容易出现意想不到的状况。今天我就遇到了一个关于Druid SQL解析异常的问题&#xff0c;具体表现为com.alibaba.druid.sql.parser.EOFParserException: EOF。通过…

42. UE5 RPG 实现火球术伤害

上一篇&#xff0c;我们解决了火球术于物体碰撞的问题&#xff0c;现在火球术能够正确的和攻击目标产生碰撞。接下来&#xff0c;我们要实现火球术的伤害功能&#xff0c;在火球术击中目标后&#xff0c;给目标造成伤害。 实现伤害功能的思路是给技能一个GameplayEffect&#x…

akSmart大带宽服务器基础配置科普

在数字化时代&#xff0c;服务器的性能和网络带宽成为业务发展的关键因素。RakSmart作为知名的服务器提供商&#xff0c;其大带宽服务器备受用户青睐。那么&#xff0c;RakSmart大带宽服务器的基础配置究竟有哪些呢?本文将为您揭开这一神秘面纱。 首先&#xff0c;我们来看看R…

阿里云X魔搭社区Create@AI创客松第四届冠军:MumuLab

4月13日终于迎来了线下Demo Day&#xff0c;此前阿里云 X 魔搭社区 X Datawhale CreateAI创客松已经紧锣密鼓地准备了一个多月时间&#xff0c;全球150团队报名、创作出66作品、评选出25支团队进入决赛&#xff0c;作品范围覆盖从办公效率到法律调解再到游戏互动以及构建童话世…

添加阿里云yum源

添加阿里云yum源 要添加阿里云的 yum 源&#xff0c;可以执行以下步骤&#xff1a; 首先&#xff0c;备份你的现有 yum 源配置文件&#xff0c;以防止意外更改&#xff1a; sudo cp /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup然后&#xf…

跟着Datawhale重学数据结构与算法(3)---排序算法

开源链接&#xff1a;【 教程地址 】【电子网站】 【写博客的目的是记录自己学习过程&#xff0c;方便自己复盘&#xff0c;专业课复习】 数组排序&#xff1a; #mermaid-svg-F3iLcKsVv8gcmqqC {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16p…

【java】equals()方法

什么是Object类 Object类是所有类的始祖&#xff0c;在Java中每个类都是由它扩展而来的。 equals()方法 在 Object 类当中&#xff0c;equals()的底层是“”&#xff0c;String 类将其重写。 和equals() 有什么区别 对于基本数据类型而言&#xff0c;比较值是否相同&#x…

污水处理设备运维注意事项有哪些

污水处理设备的运维是确保污水处理效率和处理质量的关键环节。良好的运维不仅可以延长设备的使用寿命&#xff0c;还能确保污水处理过程的稳定性和可靠性。以下是一些污水处理设备运维的重要注意事项&#xff1a; 1. 定期检查和维护 设备检查&#xff1a;定期对污水处理设备进…

FFmpeg下载教程(Windows版)

文章目录 下载地址步骤 下载地址 https://ffmpeg.org/download.html 步骤

LeetCode53. 最大子数组和

LeetCode53. 最大子数组和 解题思路dp 代码 /* 数组长度n 9,连续的区间 那区间长度为1的区间数量是&#xff0c;9个 区间长度为2的区间数量是8个 区间长度为3的连续的区间数量为7个 .... 区间长度为9的区间数量为1个 */ class Solution { public:int maxSubArray(vector<…

文献速递:肺癌早期诊断---早期肺癌诊断:基于深度学习的循环外泌体光谱分析

Title 题目 Early-Stage Lung Cancer Diagnosis by Deep Learning-Based Spectroscopic Analysis of Circulating Exosomes 早期肺癌诊断&#xff1a;基于深度学习的循环外泌体光谱分析 Abstract 摘要 Lung cancer has a high mortality rate, but an early diagnosis can …

【极速前进】20240422:预训练RHO-1、合成数据CodecLM、网页到HTML数据集、MLLM消融实验MM1、Branch-Train-Mix

一、RHO-1&#xff1a;不是所有的token都是必须的 论文地址&#xff1a;https://arxiv.org/pdf/2404.07965.pdf 1. 不是所有token均相等&#xff1a;token损失值的训练动态。 ​ 使用来自OpenWebMath的15B token来持续预训练Tinyllama-1B&#xff0c;每1B token保存一个che…

【STM32+HAL+Proteus】系列学习教程---ADC(查询、中断、DMA模式下的电压采集)

实现目标 1、学会STM32CubeMX软件关于ADC的配置 2、掌握ADC三种模式&#xff08;查询、中断、DMA&#xff09;编程 3、具体目标&#xff1a;1、将开发板单片机采集到的电压值上传至上位机串口调试助手显示。 一、ADC 概述 1、什么是ADC? ADC&#xff08;Analog to Digit…

MyBatis入门学习二(配置文件、mapper文件、动态SQL)

目录 1、映射配置文件 1.1 properties 1.2 settings 1.3 typeAliases 1.4 typeHandlers 1.5 plugins 1.5 environments 1.6 mappers 2、映射Mapper文件 2.1 CRUD 2.1.1 select 2.1.2 Insert 2.1.3 update 2.1.4 delete 2.1.5 parameterType传入多个参数 2.1.5 …

图像在神经网络中的预处理与后处理的原理和作用(最详细版本)

1. 问题引出及内容介绍 相信大家在学习与图像任务相关的神经网络时&#xff0c;经常会见到这样一个预处理方式。 self.to_tensor_norm transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]) 具体原理及作用稍后解释&…

2024年vue 开发环境 Node.js于win10环境下的安装

2024年vue 开发环境 Node.js于win10环境下的安装 导航 文章目录 2024年vue 开发环境 Node.js于win10环境下的安装导航一、下载node.js二、安装node.js三、测试(一)四、环境配置五、测试(二)六、安装淘宝镜像七、安装vue脚手架 一、下载node.js Node.js 官方网站下载&#xff…