内容简介:WebAssembly (abbreviated Wasm) is a binary instruction format for a stack-based virtual machine. Wasm is designed as a portable target for compilation of high-level languages like C/C++/Rust, enabling deployment on the web for client and server application
WebAssembly (abbreviated Wasm) is a binary instruction format for a stack-based virtual machine. Wasm is designed as a portable target for compilation of high-level languages like C/C++/Rust, enabling deployment on the web for client and server applications. WebAssembly(缩写 Wasm)是基于堆栈虚拟机的二进制指令格式。Wasm为了一个可移植的目标而设计的,可用于编译C/C+/RUST等高级语言,使客户端和服务器应用程序能够在Web上部署。
webassembly的介绍可以参考图说 WebAssembly。
本文以@ne_fe/gis这个模块的开发过程梳理webassembly如何应用到前端工程中。 注:使用emscripten完成weassembly开发至少需要基础的c/c++编码能力。
@ne_fe/gis 简介
该模块主逻辑由c++编写,webpack配合emscripten附带的emcc编译器将其编译到wasm。提供大批量坐标的经纬度转换功能,在十几万坐标点转换的情况下,依然有优秀的性能表现。 具体其他信息可以参考npm上该模块的Readme。
Emscripten的安装
emscripten是webassembly官方推出的将c/c++代码编译成wasm文件的工具。 具体安装可以参照官网文档。
Webpack配置
主要针对c++源码文件,需要添加正确的loader进行处理。使用的loader为 cpp-wasm-loader ,下面是我的webpack.config.js所写大概配置,其他配置跟普通的webpack配置大致相同。
module.exports = { ... resolve: { extensions: [ '.js', '.vue', '.c', '.cc', '.cpp', '.wasm' ], alias: { vue$: 'vue/dist/vue.esm.js', }, }, ... module: { ... { test: /\.(c|cc|cpp)$/, use: { loader: 'cpp-wasm-loader', options: { // 这里的两个参数,第一个是让emcc能够识别c++11的语法与特性 // 第二个是让emcc能够将EMSCRIPTEN_BINDINGS宏里面所指定的类与方法能够在绑定到模块导出的js对象上,让js能够直接调用 // 还可以传入其他clang编译器可接受的参数 emccFlags: existingFlags => existingFlags.concat([ '-std=c++11', '--bind' ]), // add or modify compiler flags // emccPath: "path/to/emcc", // only needed if emcc is not in PATH, memoryClass: false, // disable javascript memory management class, fetchFiles: true, asmJs: false, // 不生成wasm.js wasm: true, // 生成wasm文件 fullEnv: true, }, }, }, ... }, }; 复制代码
主要逻辑编写
emscripten的主要api可以参考官方文档上的说明,不过建议参考本地头文件(emsdk安装路径/emsdk/emscripten/1.38.22/system/include/),相比文档,本地头文件更能看得明白。
以高德地图坐标转gps坐标代码为例
// em.cc #include <math.h> #include <vector> #include <string> #include <emscripten.h> #include <emscripten/bind.h> #include <emscripten/val.h> #define PI 3.14159265 #define ee 0.00669342162296594323 #define a 6378245.0 using namespace emscripten; extern "C" { std::vector<float> gcj02towgs84(float lat, float lng); bool out_of_china(float lat, float lng); float transformlat(float lat, float lng); float transformlng(float lat, float lng); val translateFromGPSInCPP(val data, std::string target, int type); // 相应地图坐标转gps坐标 // data为坐标点数组,target为转换目标 // type 是否转换坐标对象 0 只会对数值做计算转换 1 不仅会对数值做计算转换,还会转为腾讯地图经纬度对象 // val 为c++中代表js对象的数据类型,头文件为<emscripten/val.h> val translateFromGPSInCPP(val data, std::string target, int type) { unsigned l = data["length"].as<unsigned>(); val res = val::array(); val _mid = val::object(); val mid = val::object(); val amap = val::global("AMap"); val bmap = val::global("BMap"); val _Object = val::global("Object"); val qq = val::global("qq"); for(unsigned i = 0; i < l; ++i) { val midObj = data[i]; float lat = midObj["latitude"].as<float>(); float lng = midObj["longitude"].as<float>(); std::vector<float> translateOneResult; if (target == "a") { translateOneResult = wgs84togcj02(lat, lng); // 转高德坐标 } else if (target == "b") { translateOneResult = wgs84tobd(lat, lng); // 转百度坐标,忽略 } else { translateOneResult = wgs84togcj02(lat, lng); // 转腾讯坐标,忽略 } if (type == 0) { // just translate number _mid.set<std::string, float>("latitude", translateOneResult[0]); _mid.set<std::string, float>("longitude", translateOneResult[1]); } else { if (target == "a") { if (!amap.isUndefined()) { _mid = amap["LngLat"].new_(translateOneResult[1], translateOneResult[0]); } } if (target == "b") { if (!bmap.isUndefined()) { _mid = bmap["Point"].new_(translateOneResult[0], translateOneResult[1]); } } if (target == "t") { val tmap = qq["maps"]; if (!qq.isUndefined() && !tmap.isUndefined()) { _mid = tmap["LatLng"].new_(translateOneResult[0], translateOneResult[1]); } } mid = _Object.call<val>("assign", _mid, midObj); } res.set<int, val>(i, mid); } return res; } std::vector<float> gcj02towgs84(float lat, float lng) { std::vector<float> res; bool out_of_china_res = out_of_china(lat, lng); if (out_of_china_res) { res.push_back(lat); res.push_back(lng); } else { float lng1 = lng - 105.0; float lat1 = lat - 35.0; float dlat = transformlat(lng1, lat1); float dlng = transformlng(lng1, lat1); float radlat = lat / 180.0 * PI; float magic = sin(lat / 180.0 * PI); magic = 1 - ee * magic * magic; float sqrtmagic = sqrt(magic); dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * PI); dlng = (dlng * 180.0) / (a / sqrtmagic * cos(radlat) * PI); const float mglat = lat - dlat; const float mglng = lng - dlng; res.push_back(mglat); res.push_back(mglng); } return res; } bool out_of_china(float lat, float lng) { return (lng < 72.004 || lng > 137.8347) || ((lat < 0.8293 || lat > 55.8271) || false); } float transformlat(float lat, float lng) { float ret = -100.0 + 2.0 * lat + 3.0 * lng + 0.2 * lng * lng + 0.1 * lat * lng + 0.2 * sqrt(abs(lat)); ret += (20.0 * sin(6.0 * lat * PI) + 20.0 * sin(2.0 * lat * PI)) * 2.0 / 3.0; ret += (20.0 * sin(lng * PI) + 40.0 * sin(lng / 3.0 * PI)) * 2.0 / 3.0; ret += (160.0 * sin(lng / 12.0 * PI) + 320 * sin(lng * PI / 30.0)) * 2.0 / 3.0; return ret; } float transformlng(float lat, float lng) { float ret = 300.0 + lat + 2.0 * lng + 0.1 * lat * lat + 0.1 * lat * lng + 0.1 * sqrt(abs(lat)); ret += (20.0 * sin(6.0 * lat * PI) + 20.0 * sin(2.0 * lat * PI)) * 2.0 / 3.0; ret += (20.0 * sin(lat * PI) + 40.0 * sin(lat / 3.0 * PI)) * 2.0 / 3.0; ret += (150.0 * sin(lat / 12.0 * PI) + 300.0 * sin(lat / 30.0 * PI)) * 2.0 / 3.0; return ret; } EMSCRIPTEN_BINDINGS(module) { function("translateToGPSInCPP", &translateToGPSInCPP); } } 复制代码
webassembly vs js
测试代码运行的浏览器为 chrome63 translateFromGPSInJS方法是js实现的,为了兼容不能使用webassembly技术的浏览器 同时由于新版浏览器如chrome70及以上、firefox60及以上、safari12及以上优化了数组的性能,js实现与webassembly实现效果差距不大,只使用js进行经纬度转换
import wasm from './em.cc'; async function test() { const innerModule = (await wasm.init()).emModule; const gpsarr1 = []; gpsarr1.push({ longitude: lngX, latitude: latY }); for (let i = 1; i < 50000; i++) { let lngX = 116.3; let latY = 39.9; lngX = lngX + Math.random() * 0.0005; if (i % 2) { latY = latY + Math.random() * 0.0001; } else { latY = latY + Math.random() * 0.0006; } gpsarr1.push({ longitude: lngX, latitude: latY }); } // performance Webassembly vs Js console.time('translateFromGPSInCPP'); const res1 = await innerModule.translateFromGPSInCPP(gpsarr1, 't', 0); console.timeEnd('translateFromGPSInCPP'); console.log('res1', res1); const gpsarr2 = JSON.parse(JSON.stringify(gpsarr1)); console.time('translateFromGPSInJS'); const res2 = await gpsjs.translateFromGPSInJS(gpsarr2, 't', 0); console.timeEnd('translateFromGPSInJS'); console.log('res2', res2); } test(); 复制代码
以下是7次测试50000条经纬度转换的执行耗时(ms)
1 | 2 | 3 | 4 | 5 | 6 | 7 | |
---|---|---|---|---|---|---|---|
webassembly | 317.8198 | 260.3901 | 270.0729 | 283.7041 | 351.6569 | 287.3720 | 312.5078 |
js | 2709.5219 | 2642.2451 | 2694.9921 | 2891.1311 | 3816.5019 | 2648.9201 | 3287.1430 |
最后经过测试5000、500条坐标的经纬度转换 万条数量级坐标的经纬度转换,webassembly的执行效率是js的8-10倍。 千条数量级坐标的经纬度转换,webassembly的执行效率是js的4-6倍。 百条数量级坐标的经纬度转换,webassembly的执行效率是js的1.5-2.5倍。
发布
公司的编译环境缺少emscripten,所以在容器中编译,最后发布到npm公共仓库。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。