WebAssembly应用到前端工程(上)—— webassembly模块的编写

栏目: C++ · 发布时间: 6年前

内容简介: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公共仓库。

下一篇:WebAssembly应用到前端工程(下)—— webpack和webassembly


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Practical Django Projects, Second Edition

Practical Django Projects, Second Edition

James Bennett / Apress / 2009 / 44.99

Build a django content management system, blog, and social networking site with James Bennett as he introduces version 1.1 of the popular Django framework. You’ll work through the development of ea......一起来看看 《Practical Django Projects, Second Edition》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

在线进制转换器
在线进制转换器

各进制数互转换器

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试