js 对一个字段去重_写一个N-API没那么难?
本文基于Nodejs v13.1.0
阅读本篇文章之前,请阅读前置文章:
- nodejs是如何和libuv以及v8一起合作的?(文末有彩蛋哦)
阅读完本篇文章之后,希望你可以掌握以下知识点:
- C++ Addon引入的原理
- NAN写法与NAPI写法的区别
- C++ Addon对算法效率的影响
本文demo地址:传送门
1、NAN和NAPI的历史简介
诚如从暴力到 NAN 再到 NAPI——Node.js 原生模块开发方式变迁一文所提到的NAN和NAPI的历史,NAN为了搞定”封建时代“混乱的C++原生模块,不再让一个模块只能被若干个nodejs版本使用,而提出使用宏定义来解决这个问题,所以说NAN是一大堆宏定义,兼容各种nodejs版本的宏定义。做到了一次编写,到处编译
。
而这种设计模式还是依然有缺点,那就是多次编译,也就是说你写的插件如果到了更高的Nodejs版本,还是需要再次编译,让宏定义再次匹配新的版本去编译出新的插件包,于是在node v8版本之后,nodejs提出了新的一套机制,也就是我们这次的主角-NAPI。
不同版本的 Node.js 使用同样的接口,这些接口是稳定地 ABI 化的,即应用二进制接口(Application Binary Interface)。这使得在不同 Node.js 下,只要 ABI 的版本号一致,编译好的 C++ 扩展就可以直接使用,而不需要重新编译。
那么我们怎么查看当前Node版本的ABI版本呢?通过process.versions.modules
可以打印出当前的ABI版本,nodejs提供了一份完整的ABI版本列表:
abi_version_registry.json
process.versions提供了Nodejs一些版本相关的提示,包括v8使用的版本,各个依赖包的版本,比如在版本v13.2.0下打印:(注:napi这个字段是NAPI模块版本,它有一个自己的版本矩阵:N-API Version Matrix)
{node: '13.2.0',v8: '7.9.317.23-node.20',uv: '1.33.1',zlib: '1.2.11',brotli: '1.0.7',ares: '1.15.0',modules: '79',nghttp2: '1.40.0',napi: '5',llhttp: '1.1.4',openssl: '1.1.1d',cldr: '35.1',icu: '64.2',tz: '2019c',unicode: '12.1'
}
N-API 定义了如下特性:
- 提供头文件
node_api.h
; - 任何 N-API 调用都返回一个
napi_status
枚举,来表示这次调用成功与否; - N-API 的返回值由于被
napi_status
占坑了,所以真实返回值通过形参来传递; - 所有
JavaScript
数据类型都被黑盒类型napi_value
封装,不再是类似于v8::Object
、v8::Number
等类型; - 如果函数调用不成功,可以通过
napi_get_last_error_info
函数来获取最后一次出错的信息。
1.1、既然说N-API是基于ABI的,为啥它使用的不是modules
字段,而是自定义了napi
这个字段?
这个问题是留给大家思考的。有疑问欢迎留言讨论。
2、Nodejs是如何引入执行一个C++插件的?
老规矩,如下图所示:
图中我们还发现有下面这么一个结论:为啥我们直接require
JSON文件的时候,可以自动转化为一个对象?
因为模块解析的时候,如果是json后缀的时候,会调用JSON.parse
这个方法
Tips:上图还可以作为面试题《请说说在Nodejs中require一个文件后一个简单流程》的一个简单答案
我们重点关注到v8
里面的DLOpen
方法。
该方法是为了解析node
后缀的模块而写,node
模块本质是一个动态链接库(windows下后缀是dll,linux下后缀是so),所以你看v8源码下,如果是支持POSIX标准的话,是使用系统APIdlopen()
打开即可,如果是非POSIX的话,就只能借助libuv的uv_dlopen
方法去打开。
其次文件打开之后,执行以下几个判断:
- 判断模块的初始化函数是否符合标准
- 判断是否是普通的C++插件,如果是的话,看看是否当前的nodejs版本ABI版本是否可以支持加载该模块
最后执行模块的初始化函数
这里可以看到N-API的模块是不需要判断的,从这里也印证了这句话:
A given version n of N-API will be available in the major version of Node.js in which it was published, and in all subsequent versions of Node.js, including subsequent major versions.
2.1、使用不同版本编译和执行NAN模块
利用nvm
切换nodejs版本,我们先使用node v11.15.0编译,再使用node v13.2.0执行,nodejs会报如下错误:
:16
internal/modules/cjs/loader.js:1190return process.dlopen(module, path.toNamespacedPath(filename));^Error: The module '/Users/linxiaowu/Github/nodejs-NAPI-demo/packages/md5-NAN/build/Release/md5-nan.node'
was compiled against a different Node.js version using
NODE_MODULE_VERSION 67. This version of Node.js requires
NODE_MODULE_VERSION 79. Please try re-compiling or re-installing
the module (for instance, using `npm rebuild` or `npm install`).at Object.Module._extensions..node (internal/modules/cjs/loader.js:1190:18)at Module.load (internal/modules/cjs/loader.js:976:32)at Function.Module._load (internal/modules/cjs/loader.js:884:14)at Module.require (internal/modules/cjs/loader.js:1016:19)at require (internal/modules/cjs/helpers.js:69:18)at Object.<anonymous> (/Users/linxiaowu/Github/nodejs-NAPI-demo/packages/md5-NAN/index.js:1:15)at Module._compile (internal/modules/cjs/loader.js:1121:30)at Object.Module._extensions..js (internal/modules/cjs/loader.js:1160:10)at Module.load (internal/modules/cjs/loader.js:976:32)at Function.Module._load (internal/modules/cjs/loader.js:884:14)
2.2、使用不同版本编译和执行N-API模块
同样使用上述的步骤,是可以正常执行NAPI模块的。其中v11.15.0的process.versions
:
{ node: '11.15.0',v8: '7.0.276.38-node.19',uv: '1.27.0',zlib: '1.2.11',brotli: '1.0.7',ares: '1.15.0',modules: '67',nghttp2: '1.37.0',napi: '4',llhttp: '1.1.1',http_parser: '2.8.0',openssl: '1.1.1b',cldr: '34.0',icu: '63.1',tz: '2018e',unicode: '11.0' }
而v13.2.0的process.versions
是:
{node: '13.2.0',v8: '7.9.317.23-node.20',uv: '1.33.1',zlib: '1.2.11',brotli: '1.0.7',ares: '1.15.0',modules: '79',nghttp2: '1.40.0',napi: '5',llhttp: '1.1.4',openssl: '1.1.1d',cldr: '35.1',icu: '64.2',tz: '2019c',unicode: '12.1'
}
由此验证了N-API在兼容性和编译这块做的确实够好。
3、NAN和NAPI写法对比
以demo中的为例子,分别用NAN和N-API实现了一个md5,下图是二者的对比:
我们逐条逐条分析。
Tips:NAN使用第三方包nan,N-API使用第三方包node-addon-api
3.1、头部①
从头部可以看出N-API的头文件更加干净清爽,没有那些v8
的语句。因为不需要再像NAN那样使用:
using v8::Local;
using v8::Object;
using v8::String
3.2、实现部分②
实现部分,NAN使用宏定义将实现的函数头部包裹起来,而N-API经过node-addon-api
包裹之后,更像一个正常的函数,有函数名、形参、返回值。函数体实现二者差距不是很大,除了返回值:
NAN:
info.GetReturnValue().Set(New(md5str).ToLocalChecked());
非常的v8!
而N-API:
return String::New(env, md5str,32);
非常的正常!
3.3、初始化函数③
二者的区别一看就看出差距:
NAN:
Nan::HandleScope scope;
Nan::SetMethod(exports, "md5", md5);
N-API:
exports.Set("md5", Function::New(env, GetMD5));
return exports;
3.4、模块定义④
NAN模块的初始化是交给 Node.js 提供的宏来实现的:
NODE_MODULE(addon, init)
而N-API使用自己的宏定义(NAPI_MODULE
),因为我们使用node-addon-api
,所以它也对这个宏定义包裹成下面这个了:
NODE_API_MODULE(addon, Init)
4、NAPI可以提高算法效率吗?
我们来看看使用N-API对排序算法的一个效率提升,示例中使用了两种排序算法:冒泡排序和快速排序:
代码参考:sort.cc
比如以快速排序为例子,快速排序的算法时间复杂度是NlogN,C语言版本的快排:
void quickSort(unsigned int *array, unsigned int length)
{unsigned int partition;unsigned int i, j;unsigned int rightLength, leftLength;unsigned int *rightArray, *leftArray;if (length < 2){return;}partition = *(array);i = 1;for (j = 1; j <= length; j++){if (*(array + j) < partition){swap(array + i, array + j);i++;}}swap(array, array + i - 1);leftLength = i - 1;leftArray = array;rightArray = array + i;rightLength = length - i;quickSort(rightArray, rightLength);quickSort(leftArray, leftLength);
}
当我们改变demo中test/index.js
中数组的长度,得到的js版本排序时间和N-API的排序时间(单位ms)如下图:
从图中可以看到,在快速排序算法中,随着数组长度的增加,js版本的排序时间甚至还优于N-API的排序时间,可以说二者不相上下,而在冒泡排序中,N-API的排序时间一直是优于js版本的排序时间,得出两个结论:
- 选择算法的重要性!
- v8对代码优化已经做得很好了!
参考
- 从暴力到 NAN 再到 NAPI——Node.js 原生模块开发方式变迁
- ABI Stability
- Shared library handling
- process.versions.modules is not for N-API ?
js 对一个字段去重_写一个N-API没那么难?相关推荐
- python写一个服务_写一个Python的windows服务
1. 安装pywin32和pyinstaller pip install pywin32 pip install pyinstaller 2.写一个服务Demo # -*- coding: utf-8 ...
- MySQL 实现一个字段赋值给另一个字段
MySQL 实现一个字段赋值给另一个字段 1.将同一个表中的一个字段的值复制给另一个字段UPDATE t_user SET signed_time = create_time 122.将同一个表中两个 ...
- 给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1. JAVA: class So ...
- [廖雪峰python教程列表生成器练习]杨辉三角定义如下,把每一行看做一个list,试写一个generator,不断输出下一行的list
杨辉三角定义如下: 1/ \1 1/ \ / \1 2 1/ \ / \ / \1 3 3 1/ \ / \ / \ / \1 4 6 4 1/ \ / \ / \ / \ / \ 1 5 10 10 ...
- 使用js在桌面上写一个倒计时器_论一个倒计时器的性能优化之路
原文发表于 2018.05.25,搬运自个人博客. 引子 回顾这半年,扛需求能力越来越强,业务代码也是越写越多.但稍一认真看看这些当时为了满足快速上线所码的东西,问题其实还是不少.这次就从一个简单的计 ...
- js 对一个字段去重_js面试
js面试题 1.简述同步和异步的区别 2.怎么添加.移除.复制.创建.和查找节点 3.实现一个函数clone 可以对Javascript中的五种主要数据类型(Number.string.Object. ...
- mysql 删除一个字段语句怎么写_删除的sql语句怎么写
1. 删除一个表的sql 语句怎么写啊 使用drop语句,drop table (需要删除表的名字). drop是删除整个表,delete是删除表的内容. drop语句的作用:删除内容和定义,释放空间 ...
- sql语句查询商品的一二三级分类都是一个字段怎么办_畅购商城(三):商品管理...
好好学习,天天向上 本文已收录至我的Github仓库「DayDayUP」:github.com/RobodLee/DayDayUP,欢迎Star 小练手 这里有三个小练手的任务,内容比较简单,就是对一 ...
- shell备份mysql思路_写一个shell脚本备份mysql数据库的步骤
写一个shell脚本备份mysql数据库的步骤 发布时间:2020-05-25 15:47:41 来源:51CTO 阅读:221 作者:三月 下文我给大家简单讲讲关于写一个shell脚本备份mysql ...
- 网页中用PHP设计一个计算器,用PHP写一个计算器(附完整代码)_后端开发
PHP作用域和文件夹操作示例_后端开发 php中的作用域有:变量作用域.静态变量.匿名函数use,函数内部不能访问函数外部的变量,但在匿名函数中,可以通过use将外部变量引入匿名函数中. 本篇文章介绍 ...
最新文章
- Unity NGUI ScrollView 苹果式滑动
- php中strrpos函数的返回值类型是型_PHP常用函数总结
- FPGA篇(八)FPGA学习经典网站推荐 (转)
- ExtJS 4.1有什么值得期待?
- [习题].FindControl()方法 与 PlaceHolder控件 #2(动态加入「子控件」的事件)
- java day17 【线程、同步】
- AS星尘粒子系统 初识2
- Verilog实现千兆以太网传输
- 第二章 2.群中的等价关系 -- 陪集,共轭,正规子群与商群
- 工业设备软件的研发测试
- “知识共享”早期版本是什么样子?
- 如何在CentOS 7系统搭建企业常用的远程yum仓库,详细教学!
- uiautomator2+python实现企业微信自动打卡
- autoit 中文文档:
- python中的sort排序加换行_python中sort()排序的方法
- IOS开发 阅读器类APP可用开源框架介绍(2)
- 不用写代码,用表格居然能开发软件和APP
- 2016年我的看电影计划
- 研究生开题报告评议意见计算机,对开题报告评议意见
- Microsoft VS Code绿色版及备份制作
热门文章
- MVC 19个通信(请求)管道
- java获取当前项目或类路径
- Unity Shader 噪声消融特效 - 剑灵死亡特效
- ASP.NET没有魔法——ASP.NET MVC使用Area开发一个管理模块
- Injection with CDI (Part I)
- Could not load file or assembly 'MagickNet.dll'
- asp.net实现页面无刷新效果
- Interesting Finds: 2008.01.25
- Word 相关的快捷键
- Json 读写操作中含有中文时