本文基于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::Objectv8::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对代码优化已经做得很好了!

参考

  1. 从暴力到 NAN 再到 NAPI——Node.js 原生模块开发方式变迁
  2. ABI Stability
  3. Shared library handling
  4. process.versions.modules is not for N-API ?

js 对一个字段去重_写一个N-API没那么难?相关推荐

  1. python写一个服务_写一个Python的windows服务

    1. 安装pywin32和pyinstaller pip install pywin32 pip install pyinstaller 2.写一个服务Demo # -*- coding: utf-8 ...

  2. MySQL 实现一个字段赋值给另一个字段

    MySQL 实现一个字段赋值给另一个字段 1.将同一个表中的一个字段的值复制给另一个字段UPDATE t_user SET signed_time = create_time 122.将同一个表中两个 ...

  3. 给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。

    给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1. JAVA: class So ...

  4. [廖雪峰python教程列表生成器练习]杨辉三角定义如下,把每一行看做一个list,试写一个generator,不断输出下一行的list

    杨辉三角定义如下: 1/ \1 1/ \ / \1 2 1/ \ / \ / \1 3 3 1/ \ / \ / \ / \1 4 6 4 1/ \ / \ / \ / \ / \ 1 5 10 10 ...

  5. 使用js在桌面上写一个倒计时器_论一个倒计时器的性能优化之路

    原文发表于 2018.05.25,搬运自个人博客. 引子 回顾这半年,扛需求能力越来越强,业务代码也是越写越多.但稍一认真看看这些当时为了满足快速上线所码的东西,问题其实还是不少.这次就从一个简单的计 ...

  6. js 对一个字段去重_js面试

    js面试题 1.简述同步和异步的区别 2.怎么添加.移除.复制.创建.和查找节点 3.实现一个函数clone 可以对Javascript中的五种主要数据类型(Number.string.Object. ...

  7. mysql 删除一个字段语句怎么写_删除的sql语句怎么写

    1. 删除一个表的sql 语句怎么写啊 使用drop语句,drop table (需要删除表的名字). drop是删除整个表,delete是删除表的内容. drop语句的作用:删除内容和定义,释放空间 ...

  8. sql语句查询商品的一二三级分类都是一个字段怎么办_畅购商城(三):商品管理...

    好好学习,天天向上 本文已收录至我的Github仓库「DayDayUP」:github.com/RobodLee/DayDayUP,欢迎Star 小练手 这里有三个小练手的任务,内容比较简单,就是对一 ...

  9. shell备份mysql思路_写一个shell脚本备份mysql数据库的步骤

    写一个shell脚本备份mysql数据库的步骤 发布时间:2020-05-25 15:47:41 来源:51CTO 阅读:221 作者:三月 下文我给大家简单讲讲关于写一个shell脚本备份mysql ...

  10. 网页中用PHP设计一个计算器,用PHP写一个计算器(附完整代码)_后端开发

    PHP作用域和文件夹操作示例_后端开发 php中的作用域有:变量作用域.静态变量.匿名函数use,函数内部不能访问函数外部的变量,但在匿名函数中,可以通过use将外部变量引入匿名函数中. 本篇文章介绍 ...

最新文章

  1. Unity NGUI ScrollView 苹果式滑动
  2. php中strrpos函数的返回值类型是型_PHP常用函数总结
  3. FPGA篇(八)FPGA学习经典网站推荐 (转)
  4. ExtJS 4.1有什么值得期待?
  5. [习题].FindControl()方法 与 PlaceHolder控件 #2(动态加入「子控件」的事件)
  6. java day17 【线程、同步】
  7. AS星尘粒子系统 初识2
  8. Verilog实现千兆以太网传输
  9. 第二章 2.群中的等价关系 -- 陪集,共轭,正规子群与商群
  10. 工业设备软件的研发测试
  11. “知识共享”早期版本是什么样子?
  12. 如何在CentOS 7系统搭建企业常用的远程yum仓库,详细教学!
  13. uiautomator2+python实现企业微信自动打卡
  14. autoit 中文文档:
  15. python中的sort排序加换行_python中sort()排序的方法
  16. IOS开发 阅读器类APP可用开源框架介绍(2)
  17. 不用写代码,用表格居然能开发软件和APP
  18. 2016年我的看电影计划
  19. 研究生开题报告评议意见计算机,对开题报告评议意见
  20. Microsoft VS Code绿色版及备份制作

热门文章

  1. MVC 19个通信(请求)管道
  2. java获取当前项目或类路径
  3. Unity Shader 噪声消融特效 - 剑灵死亡特效
  4. ASP.NET没有魔法——ASP.NET MVC使用Area开发一个管理模块
  5. Injection with CDI (Part I)
  6. Could not load file or assembly 'MagickNet.dll'
  7. asp.net实现页面无刷新效果
  8. Interesting Finds: 2008.01.25
  9. Word 相关的快捷键
  10. Json 读写操作中含有中文时