【AICon】探索八个行业创新案例,教你在教育、金融、医疗、法律等领域实践大模型技术! >>> 了解详情
写点什么

JavaScript 如何应用在 IoT?

  • 2019-07-05
  • 本文字数:3921 字

    阅读完需:约 13 分钟

JavaScript如何应用在IoT?

在日前的 GMTC 全球大前端技术大会上,Rokid 基础平台研发中心的研发工程师刘亚中发表了《JavaScript in IoT》的演讲,本文整理内容如下。

什么是 IoT

如字面翻译,即物联网,但真正要解释起来,其实就是两点:



  • IoT 是面向服务的 UI。



  • IoT 面临资源受限的问题。



在物联网时代,我们不再像从前那样独立地使用某个固定、单一的产品,而是在享受着整个环境或者是网络给我们提供的服务,比如原来我们买一个闹钟回来,闹钟就是闹钟,在 IoT 环境下,闹钟是其中的一环,当它叫醒你后,整个系统会为你准备起床后需要的所有待办事物,这就是物联网。


接下来我们来看看资源受限的问题:



最右侧的手机自不必说,整个生态已经相当成熟了。最左侧的是目前的低端配置,可以看到内存和可用空间都是 MB 为单位的,CPU 也相当受限,因此这种设备上 Linux 也已经不满足运行的最低需求了,所以一般是采用更轻量级的 RTOS,流行的有 FreeRTOS 和 RT-Thread(国内)。


最中间的是以 Linux 为主的 128+128 组合的设备,最为我们所熟知的就是智能音箱(无屏),对于这类设备来说包括 Alexa、Rokid、小爱同学等,现在大多数厂商在设备端的开发语言也都以 C + Lua 为主,目前也仅有 Rokid 支持 JavaScript 直接运行在设备端,而本文也主要是针对智能音箱这一挡设备来展开。


然后我们来看下,为什么在 128 MB 的设备上运行 JavaScript 也如此困难呢?



上图是 Rokid 智能音箱上各个子模块的内存占用情况:



  • kernel 内核占用,用于保证 Linux 用户态程序能正常运行。



  • dsp 包括从麦克风读出语音数据,然后进行算法处理,最终获取是否语音激活。



  • system 包括一些底层系统功能,如 IPC 服务、多媒体服务、存储服务等。



  • applications 即上层应用的逻辑。



通过上图的比例最终可以看出,JavaScript 真正能用的内存只有 25MB,这对于目前一个 Node.js 进程来说也就刚刚好而已,这也是目前大多数音箱,仅在服务器上提供 JavaScript SDK 的原因。

Why JavaScript?

为什么我们要在 IoT 使用 JavaScript 呢?这里简单给出几个理由。首先 Web 已经是一个相当成熟的社区了,它聚集了大量的开发者,这对于任何一个开放平台来说,都是一个非常有吸引力的开发者来源。


另外得益于 JavaScript 或者说 Web 这种即时更新的机制,在解决设备碎片化问题上,有着天然的优势,因此对于 IoT 的碎片化问题,我们希望借助于 Web 来解决。


另外,Web 标准组织已经提出了 WOT —— Web of Things 的概念,因此对于我们来说,要做的就是跟标准组织一起推进 WOT 的落地,这也是一个非常顺理成章的事情。


如果你想参与更多前端技术交流,获取更多专家分享,可以加入我们的“前端技术交流群“,社群内会经常讨论前端相关的技术、分享免费学习资料,我们也会邀请前端专家进行社群分享、直播、公开课等活动。如果你感兴趣,欢迎添加社群管理员微信 GeekUni004,回复“前端群”申请入群。

ShadowNode —— Node.js on IoT

ShadowNode 截止目前为主,已经支持如下特性:



  1. 支持 macOS 与 Linux 的编译和运行。



  2. 支持 x86、arm 与 aarch64。



  3. 支持大部分核心的 Node.js API,如:assert / buffer / N-API Add-ons / child process / crypto / dns / events / file system / http / https / module / net / os / process / timers / TLS / UDP



  4. 支持了 WebSocket / MQTT 等流行的 IoT 库



  5. 支持 CPU 和 Heap 的 Profiler



  6. 支持 N-API



接下来,关于 ShadowNode 的历史大家可以去看之前的专栏文章:



简单来说呢,ShadowNode 就是为了能让 JavaScript 能愉快地跑在 IoT 设备上而存在的,下面是 ShadowNode 与 Node.js 的一些资源占用上的对比:



可以看出无论是 macOS 上,还是 ARM 上,ShadowNode 在内存占用上有明显地提升,还记得我们之前看到智能音箱上各子模块的内存分布吗?对于应用来说可用内存有 25MB,如果使用 ShadowNode 作为运行时的话,基本上就达到了可以随意新增本地应用的状态了。



然后是启动时间,对于设备端上的应用来说,往往为了省内存,会把不重要,或不再使用的应用(进程)杀掉,当有需要时,再重新启动,因此这对进程的启动时间,包括 CPU 占用都提出了不小的要求,可以看出 ShadowNode 在这方面的表现也优于 Node.js。

N-API on ShadowNode


N-API 作为 Node.js Add-on 的 ABI 兼容的接口,可以让任何程序在不需要重新编译的情况下无缝运行在 node-chakracore 与 node-v8 上,ShadowNode 同样如此,我们按照 N-API 的标准文档和测试集,实现了在 ShadowNode 上的 N-API,这样在不需要重新编译的情况下,也能在 Node.js 和 ShadowNode 跨运行时运行,这使得我们可以根据不同的设备配置,选择合适的运行时,而上层的代码则不需要任何修改。


ShadowNode 的 N-API 完全是基于 Node.js 仓库下的 N-API 测试用例测试的,除了一些我们还不支持的特性外,其余的测试用例都会在 ShadowNode CI 上做验证,所以对于稳定性方面大可放心使用。

性能

我们在 IoT 下,针对一些特定场景,也做了一些性能优化。


比如需要使用一些第三方仓库时,仓库本身太过臃肿,导致在设备上加载起来就已经很花时间了(ShadowNode 没有 JIT),甚至于大多数情况是加载不进来的,因为这些库会在堆里创建大量的对象,而 ShadowNode 有预置的 heap_maximum_size,这样难道就没有其他办法了吗?


后面我们想到了一个办法,那就是保持兼容这些第三方库的 JS API,底层全部用 C/C++ 重写,这样可以减少大量的对象创建,同时也能让开发者使用时没有任何差异,我们使用这种方式分别完成了 WebSocket 和 MQTT 在 ShadowNode 上的移植。


另外一个优化手段是引入 NODE_PRIORITIZED_PATH,大家肯定对 NODE_PATH 不陌生吧,那 NODE_PRIORITIZED_PATH 理解起来也不困难,就是一个最高优先级的 NODE_PATH。因为在 IoT 设备上的情况与服务端往往不同,Node.js 模块都是放在每个项目中的,然而在 IoT 设备上的每个应用都比较轻,因此大部分依赖库都是放在全局的 node_modules,这样就导致每次 require 时,总会从模块的当前路径去搜索,这样造成了大量的浪费。


因此我们引入了 NODE_PRIORITIZED_PATH 来设置为全局路径,这样帮我们节省了 30%的启动时间。


接下来是 Copy-on-Write 技术(以下简称 COW),它是 Linux 针对 fork 函数的优化方法,可以节省进程启动时间与内存。这里首先要科普一个知识,即 child_process.fork 并不是真正的 fork,他依然要让子进程从零开始执行,并且抛弃掉父进程的所有资源。


我们来看一下下面的代码:


function uv_spawn (file, args) {  var pid = fork()  if (pid === 0) {    execvp(file, args) *// this disables COW*    *// starting VM and load script*  }}uv_spawn(‘test.js’, [])
复制代码


在 Node.js 中,无论是 spawn、exec 还是 fork,都调用了同一个 uv_spawn 函数,以上是该函数的伪代码(JavaScript 版本)。可以看到每次 fork 完,都会在子进程调用 execvp 来重新初始化子进程,一旦使用了 execvp 这个函数,就意外着系统将把 COW 禁用了,我们再来看看不调用 execvp 的情况下,如何写代码:


var fork = require(‘linux-sys’).fork
*// load common modules for children*var player = require(‘player’)var http = require(‘http’)var foobar = require(‘foobar’)
*// start forking*var pid = fork()if (pid == 0) { *// here is the child process* *// use player / http / foobar*}
复制代码


上面的代码不再是伪代码了,我们通过将系统的 fork 使用 N-API 暴露给 JavaScript 层,然后从 fork 开始到子进程真正能使用 player、http 和 foobar 模块,仅花了 4ms。


当然,上面的示例代码只是为了证明 COW 拥有卓越的性能提升,因此我们后来就创建了一个新项目 —— Hive。


yodaos-project/hive:https://link.zhihu.com/?target=https%3A//github.com/yodaos-project/hive)


github.comhttps://link.zhihu.com/?target=https%3A//github.com/yodaos-project/hive)


Hive 是一个独立于 ShadowNode 的子项目,可以运行在 Node.js 与 ShadowNode 上,因此我们基于 Node.js,对 hive-fork、nodejs-fork、nodejs-spawn 做了一组对比测试:



我们得出的结论显而易见,Hive 在启动速度上几乎是完胜 child_process,这对于设备端的应用启动加速具有很大的意义。


同时在 FaaS 时代,我相信 Hive 也能占有一席之地,FaaS 典型的场景就是快速启动一个进程来执行,然后退出,使用 Hive 可以轻松地定义脚本中的 API,定义好之后,便不需要担心进程启动后所带来的加载消耗,因为几乎是 0 成本的。


YodaOS 从诞生的第一个版本,到现在已经快 1 年时间了。接下来小小预告一下,下半年我们准备发布第二个大版本,即 YodaOS 8.0。在这个版本中,YodaOS 与 Rokid 开放平台完成了解耦,本地的应用也采用了大家熟悉的 Web 应用(不完全兼容)。届时,开发者可以使用 YodaOS 来接入 Alexa、天猫精灵或者 Google Assistant。

总结

很开心能够参加 GMTC 这样的活动,不管是作为讲师还是听众,都收获颇多,我相信 JavaScript 一定会在 IoT 时代释放更多开发者的能量。

嘉宾介绍

刘亚中,开源爱好者,Node.js Collaborator、ShadowNode 作者,目前主攻:Node.js 在 AIoT 领域的应用, 并负责 YodaOS 的社区推广工作。五年 JavaScript 开发经验,曾就职于:SeedMail、Pixbi、阿里巴巴,目前就职于 Rokid 基础平台研发中心,主要工作方向为基于 JavaScript 的物联网操作系统。近年来参与并负责 YodaOS 项目,将 IoT 和 AI 能力开放给 Node.js 社区,并实现了 Node.js 在 IoT 场景的产品级落地。同时也是 Node.js Collaborator、ShadowNode 和 TensorFlow-Node.js 作者,开源发烧友,目前给 60 个开源项目贡献过代码。


2019-07-05 17:594767

评论

发布
暂无评论
发现更多内容

Spring XML 原理

gin

xml spring 自定义标签

Go- 字符串处理

HelloBug

Go 语言 字符串处理

ShardingSphere源码解析 初步准备

Java 源码 ShardingSphere

Android.mk

Changing Lin

8月日更

为了完成小姐姐安排的打分系统,又熬了一个小时的夜补充视图与模板

梦想橡皮擦

8月日更

回调模式

wzh

面试 设计模式 回调函数

类加载机制

wzh

Java 面试 JVM 类加载器 JVM类加载

Vue进阶(五十六):vue-cli 脚手架 karma.conf.js 配置文件详解

No Silver Bullet

Vue 8月日更

策略模式

wzh

面试 设计模式 策略模式

JVM GC机制

wzh

面试 JVM 垃圾回收 GC GC Root

Go- 字符串

HelloBug

索引 字符串 Go 语言 拼接 长度

JAVA 规范

gin

架构实战训练营模块1 作业

Sonichen

java操作sql server数据库

Python研究者

8月日更

观察者模式

wzh

面试 设计模式 观察者模式

JVM内存划分

wzh

面试 JVM 方法区

MySQL 系列教程之(十一)Explain 与慢查询优化

若尘

MySQL 数据库 8月日更

从程序与机器码看低代码演进方向

李印

编程 低代码

IDEA2020.1构建Spring5.2.x源码

4ye

Java spring 源码 后端 8月日更

Nginx-基本概念和使用

Rubble

8月日更

【布道API】权限错误码选择:401、403 或 404

devpoint

HTTP Authorization REST API 8月日更

装饰器模式

wzh

面试 设计模式 装饰器

Vue进阶(五十五):vue-cli 脚手架 build.js 配置文件详解

No Silver Bullet

Vue 8月日更

Golang协程之了解管道的缓存能力

Regan Yue

协程 Go 语言 8月日更

Java技术开发专题系列之【Guava Collections】实战使用相关Guava不一般的集合框架

洛神灬殇

Java Guava 8月日更 Guava Collections

@ConditionOnClass的使用

Rubble

8月日更

vue入门:vuex概括与使用

小鲍侃java

8月日更

kubernetes/k8s CNI 分析 - 容器网络接口分析

良凯尔

Kubernetes 源码分析 Kubernetes Plugin #Kubernetes# cni

Java web程序的运行时环境

wzh

Java tomcat 面试 Web JVM

Vue进阶(五十四):vue-cli 脚手架 dev-server.js 配置文件详解

No Silver Bullet

Vue 8月日更

网络攻防学习笔记 Day113

穿过生命散发芬芳

网络攻防 8月日更

JavaScript如何应用在IoT?_语言 & 开发_刘亚中_InfoQ精选文章