GMTC全球大前端技术大会(北京站)门票9折特惠截至本周五,点击立减¥480 了解详情
写点什么

JavaScript 如何应用在 IoT?

2019 年 7 月 05 日

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 年 7 月 05 日 17:594147

评论

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

可读代码编写炸鸡二(上篇) - 命名的长度

多选参数

代码 代码组织 代码规范

训练营第三周

Just顾

架构学习第三周作业

乐天

架构师0期03周总结

我在终点等你

Breaking through Three Common Engineering Myths·英语阅读笔记

3.141516

架构师训练营 0 期第三周

Blink

【架构思维 - 学习总结】week03

chun1123

学习 设计模式

架构师训练营-作业3

进击的炮灰

小师妹学JVM之:java的字节码byte code简介

程序那些事

Java JVM Java 25 周年 bytecode 字节码

架构师训练营 - Lesson Week 3

brave heart

极客大学架构师训练营

【架构师训练营 - week3 -2】总结

早睡早起

蟒周刊/426: DjangoCon US 2020 取消了

ZoomQuiet大妈

Python 大妈 蟒营® Weekly 蟒周刊

新手村:最适合新手的 Redis 基础

多选参数

数据库 redis redis6.0.0

面向对象的设计模式

WW

第三周学习总结

Just顾

【第三周】命题作业——单例及组合模式

三尾鱼

极客大学架构师训练营

【架构思维学习】 week03

chun1123

极客大学架构师训练营 框架开发 上课总结 第五课

John(易筋)

极客时间 设计模式 极客大学 极客大学架构师训练营 框架开发

架构师训练营-总结3

进击的炮灰

可读代码编写炸鸡二(下篇) - 命名的歧义

多选参数

代码 代码优化 代码组织 代码规范

架构师训练营:第三周总结

zcj

「架构师训练营」第 3 周作业 - 总结

森林

架构师训练营第三周作业

极客大学架构师训练营

架构师训练营 - Task Week 3

brave heart

极客大学架构师训练营

2020互联网公司端午节礼盒合集!你最中意哪一款?

Java小咖秀

互联网人 端午节

「架构师训练营」第3 周作业 - 组合模式

森林

架构师训练营第三周总结

养乐多

设计模式-第三周

X﹏X

架构师训练营第三周-学习总结

【架构师训练营 - week3 -1】作业

早睡早起

架构师训练营第三周课后作业

赵凯

设计模式

JavaScript如何应用在IoT?-InfoQ