preface

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(abbreviated Wasm) is a binary instruction format based on the stack virtual machine. Designed with a portable goal in mind, Wasm can be used to compile high-level languages such as C/C+/RUST to enable client and server applications to be deployed on the Web.

For an introduction to WebAssembly, see the webAssembly diagram.

In this paper, the development process of @ne_FE/GIS module is used to comb out how WebAssembly is applied to front-end engineering. Note: Completing WeAssembly development with Emscripten requires at least basic C/C ++ coding ability.

@ne_fe/gisIntroduction to the

The main logic of this module is written in c++ and compiled into wasm by webpack with the emcc compiler that comes with emscripten. Provides a large number of coordinates of latitude and longitude conversion function, in the case of tens of thousands of coordinates point conversion, still has excellent performance. See the Readme for this module on NPM for additional information.

The installation of Emscripten

Emscripten is webAssembly’s official tool for compiling C/C ++ code into WASM files. For details about installation, refer to the official documentation.

Webpack configuration

Mainly for c++ source files, you need to add the correct loader for processing. The loader used is cp-wasM-loader, the following is the general configuration of my webpack.config.js, other configurations are roughly the same as the normal webpack configuration.

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: {// the first parameter is to enable emcc to recognize the syntax and features of c++11. // the second parameter is to enable emcc to bind the classes and methods specified in the EMSCRIPTEN_BINDINGS macro to the js objects derived from the module. EmccFlags => existingFlags. Concat ([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// do not generate wasm.true// Generate wASM file fullEnv:true,},},},... }};Copy the code

Main logic writing

Emscripten main apis can refer to the instructions on the official document, but suggested that reference local header files (emsdk installation path/emsdk emscripten 1.38.22 / system/include /), compared with the document, the local header file can see understand more.

Take amAP coordinates to GPS coordinates code as an example

// 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); // Map coordinates to GPS coordinates // data is the point group of coordinates, target is the conversion target //type// val is a c++ data type that represents a js object. The header file is <emscripten/val.h> val translateFromGPSInCPP(val data, STD ::string target, inttype) {
    unsigned l = data["length"].as<unsigned>();
    val res = val::array();
    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); // go to the coordinates, ignore}else{ translateOneResult = wgs84togcj02(lat, lng); // Change the coordinates, ignore}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]);
          }
        }
        _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 {
      floatLng1 = LNG-105.0;floatLat1 = LAT-35.0;float dlat = transformlat(lng1, lat1);
      float dlng = transformlng(lng1, lat1);
      floatRadlat = lat / 180.0 * PI;floatMagic = sin(lat / 180.0 * PI); magic = 1 - ee * magic * magic;floatsqrtmagic = sqrt(magic); Dlat = (dlat * 180.0)/((a * (1-ee))/(magic * sqrtmagic) * PI); DLNG = (DLNG * 180.0)/(a/sqrtMagic * cos(radlat) * PI); constfloat 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) {
    floatRet = -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 + = sin (20.0 * * PI (LNG) + 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) {
    floatRet = 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); }}Copy the code

webassembly vs js

The test code is running in the browser chrome63 translateFromGPSInJS. The method is implemented in JS. In order to be compatible with browsers that cannot use webassembly technology, and because new browsers such as chrome70 and above, firefox60 and above, safari12 and above optimize the performance of the array, js implementation and webassembly implementation effect gap is not big, only use js for latitude and longitude conversion

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++) {
    letLngX = 116.3;letLatY = 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(a);Copy the code

Below is the execution time (ms) of the 50000 latitude and longitude conversion in 7 tests

  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

Finally, after testing the latitude and longitude conversion of 5000 and 500 coordinates and the latitude and longitude conversion of 10,000 coordinates, WebAssembly’s execution efficiency is 8-10 times that of JS. With thousands of coordinates of latitude and longitude conversion, WebAssembly is 4-6 times more efficient than JS. Webassembly performs 1.5 to 2.5 times more efficiently than JS with a hundred orders of magnitude of latitude and longitude conversion.

release

The company’s build environment lacked Emscripten, so it was compiled in a container and eventually published to the NPM common repository.

WebAssembly to Front-end Engineering (Part 2) – WebPack and WebAssembly