资讯专栏INFORMATION COLUMN

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

Mr_houzi / 1800人阅读

摘要:本文以这个模块的开发过程梳理如何应用到前端工程中。注使用完成开发至少需要基础的编码能力。具体其他信息可以参考上该模块的。配置主要针对源码文件,需要添加正确的进行处理。下一篇应用到前端工程下和

前言
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 
#include 
#include 
#include 
#include 
#include 

#define PI 3.14159265
#define ee 0.00669342162296594323
#define a 6378245.0

using namespace emscripten;

extern "C"
{  
  std::vector 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对象的数据类型,头文件为
  val translateFromGPSInCPP(val data, std::string target, int type) {
    unsigned l = data["length"].as();
    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 lng = midObj["longitude"].as();
      std::vector 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("latitude", translateOneResult[0]);
        _mid.set("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("assign", _mid, midObj);
      }
      res.set(i, _mid);
    }
    return res;
  }

  std::vector gcj02towgs84(float lat, float lng) {
    std::vector 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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/104895.html

相关文章

  • WebAssembly应用前端工程(下)—— webpack和webassembly

    摘要:然而在当前以为主要编译工具的实际工程应用中依然存在问题。涉及到的技术主要为模块机制插件编写与插件编写。使用可以参考或,欢迎。上一篇应用到前端工程上模块的编写 在上一篇文章WebAssembly应用到前端工程(上)—— webassembly模块的编写中,完成了@ne_fe/gis模块的编写与发布。然而webassembly在当前以webpack4为主要编译工具的实际工程应用中依然存在问...

    RichardXG 评论0 收藏0
  • JavaScript工作原理(六):WebAssembly比较分析和特定情况下最好在JavaScri

    摘要:我们将拆分来分析它的工作原理,更重要的是,它在性能方面如何提升加载时间,执行速度,垃圾回收,内存使用率,平台访问,调试,多线程和可移植性。目前,是围绕和用例设计的。多线程在单个线程上运行。目前不支持多线程。被设计为安全和便携。 我们将拆分WebAssembly来分析它的工作原理,更重要的是,它在性能方面如何提升JavaScript:加载时间,执行速度,垃圾回收,内存使用率,平台API访...

    novo 评论0 收藏0
  • JavaScript与WebAssembly进行比较

    摘要:目前,是围绕和用例设计的。多线程在单个线程上运行。目前不支持多线程。 本文由云+社区发表作者:QQ音乐前端团队 在识别和描述核心元素的过程中,我们分享了构建SessionStack时使用的一些经验法则,这是一个轻量级但健壮且高性能的JavaScript应用程序,以帮助用户实时查看和重现其Web应用程序的缺陷。 这次我们来分析WebAssembly的工作原理,以及在如下几个方面和Ja...

    IntMain 评论0 收藏0
  • JavaScript与WebAssembly进行比较

    摘要:目前,是围绕和用例设计的。多线程在单个线程上运行。目前不支持多线程。 本文由云+社区发表作者:QQ音乐前端团队 在识别和描述核心元素的过程中,我们分享了构建SessionStack时使用的一些经验法则,这是一个轻量级但健壮且高性能的JavaScript应用程序,以帮助用户实时查看和重现其Web应用程序的缺陷。 这次我们来分析WebAssembly的工作原理,以及在如下几个方面和Ja...

    617035918 评论0 收藏0
  • 前端每周清单半年盘点之 WebAssembly

    摘要:前端每周清单专注前端领域内容,以对外文资料的搜集为主,帮助开发者了解一周前端热点分为新闻热点开发教程工程实践深度阅读开源项目巅峰人生等栏目。利用降低三倍加载速度自推出之后,很多开发者都开始尝试在小型项目中实践,不过尚缺大型真实案例比较。 前端每周清单专注前端领域内容,以对外文资料的搜集为主,帮助开发者了解一周前端热点;分为新闻热点、开发教程、工程实践、深度阅读、开源项目、巅峰人生等栏目...

    Alan 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<