为什么你应该在 OpenResty 项目中使用 lua-resty-core
lua-resty-core 是什么?
lua-resty-core 是 OpenResty 组件的一部分。它由两部分组成,一部分是 resty.core.*
,提供了对 lua-nginx-module Lua 接口的替换实现;另一部分是 ngx.*
,OpenResty 新的接口一般都会放到这里。
跟其他 lua-resty 开头的库一样,lua-resty-core 也是用 Lua 实现的。说到这有人可能会问,既然 lua-nginx-module 已经有了一套 API,为什么还要在 lua-resty-core 里面重新实现一次,而且还是用 Lua?
需要澄清下,lua-nginx-module 提供的 API,并不是完全意义上的用 C 实现的。准确来说,是通过 C 实现,并通过 Lua CFunction 暴露出来的。而 lua-resty-core 提供的 API,也不是表面看上去那样用 Lua 实现的。准确来说,是通过在 lua-nginx-module 里面的以 *_lua_ffi_*
形式命名的 C 函数实现的,并在 lua-resty-core 里面通过 LuaJIT FFI 暴露出来的。所以其实两者都是 C 实现。两者的比较,应该是 Lua CFunction 和 LuaJIT FFI 的比较。
那 LuaJIT FFI 有着怎样的优点,值得在已有一套的基于 Lua CFunction 的接口的前提下,去费大力气重新实现一遍?
FFI + JIT
LuaJIT FFI 的实现深深地根植于解释器自身。如果当前 LuaJIT 正处于 JIT 模式,它会在 FFI 调用时优化 Lua 领域和 C 领域间传参和返回的过程,因此采用 FFI 要比直接调用 Lua CFunction 要快。至于能快多少,则取决于调用时两个领域间数据交换频繁情况。
举个例子,
init_by_lua_block {-- 注释下面一行来禁用 lua-resty-corerequire 'resty.core'
}location /foo {content_by_lua_block {local s = ("test"):rep(256)local start = ngx.now()for _ = 1, 1e6 dongx.md5(s)endngx.update_time()ngx.say(ngx.now() - start)}
}
在启用了 lua-resty-core 的情况下(走 FFI 路径),用时是
¥ curl localhost:8888/foo
2.6159999370575
禁用 lua-resty-core 后(走 CFunction 路径),用时是
¥ curl localhost:8888/foo
2.664999961853
两者并无明显区别。
不过换个需要跟 C 领域频繁交互的调用,
local s = ("test"):rep(256)
local start = ngx.now()
for _ = 1, 1e8 dongx.ctx.test = slocal r = ngx.ctx.test
end
ngx.update_time()
ngx.say(ngx.now() - start)
启用了 lua-resty-core,用时
¥ curl localhost:8888/foo
1.800999879837
禁用后用时
¥ curl localhost:8888/foo
38.345999956131
两者便有天壤之别。
跟 ngx.ctx
一样,会收益于 FFI + JIT 的接口,还有 ngx.shared.dict
和 ngx.re
这样两类。(当然对它们的加成相对没有那么显著)
前面在提到 FFI 优化的时候,我特意强调了“当前 LuaJIT 正处于 JIT 模式”。如果当前 LuaJIT 处于解析器模式,很不幸,FFI 调用会比 CFunction 的形式慢。
在继续之前,先跳出 FFI 的话题,介绍下 LuaJIT 的 JIT 原理。
LuaJIT 是 tracing JIT Compiler。它的 JIT 是基于分支(循环或者函数)的。对于每个 tracing 的分支,它会维护一个计数器。一旦某个分支足够热,LuaJIT 会把该分支编译掉,并用编译掉的结果替换原来的代码。这要求一点:整个分支都需要是可被编译的。如果分支中有不能编译的语句,LuaJIT 会中断 tracing,该分支也就一直没法被 JIT 掉。这种不能被编译的语句,在 LuaJIT 里面叫 NYI。可以在 http://wiki.luajit.org/NYI 查看当前的 NYI 列表。
查看 JIT trace 结果很容易,仅需在 init_by_lua_block
里添加下面两行:
local v = require "jit.v"
v.on("/tmp/dump")
require "resty.core" -- 确保 lua-resty-core 是启用的
运行之后就能在 /tmp/dump 里查看 trace 情况了。在我们的例子里,结果只有一行:
[TRACE 1 content_by_lua(nginx.conf:21):4 loop]
它表示 content_by_lua
第 4 行有一个循环,能够被完整地 trace 掉。
如果想了解更详细的情况,可以改用下面两行:
local dump = require "jit.dump"
dump.on(nil, "/tmp/dump")
这时候它会记录更详细的内容,包括 trace 的过程、IR 和 mcode 的生成情况。当 LuaJIT 中断 tracing 时,你可以凭 dump 下来的内容找出它是在哪里中断的。
回归正题。让我们找个 NYI 语句,插入到循环中,比如下面这样:
local t = {}
for _ = 1, 1e6 dongx.md5(s)next(t)
end
重新跑下,用时
¥ curl localhost:8888/foo
2.9719998836517
比调用 Lua CFunction 时要慢一些。
欲抑先扬,欲扬先抑。即使解释器模式下 FFI 会明显地慢,但有些时候还是比 CFunction 快一些。比如前面的 ngx.ctx
这个例子,在解释器模式下,它的用时是:
¥ curl localhost:8888/foo
19.00200009346
慢得要命,但还是 CFunction 版本的两倍。
如果担心项目支持的 NYI 语句太多,启用 lua-resty-core 会导致性能不升反降,那么我插一句:Lua CFunction 调用就是一种 NYI 语句,而 FFI 调用是可以 JIT 的。也即是说,启用 lua-resty-core 会减少项目中一类 NYI 语句的存在。这算是切换到 lua-resty-core 的另一个理由了。
lua-resty-core 寄托着 OpenResty 的未来
你可能会觉得,JIT 啊、NYI 啊什么的离我太远了,我们的项目不需要什么性能上的优化,所以也无需引入 lua-resty-core。
OK,即使不考虑性能,你也应该引入 lua-resty-core。春哥(OpenResty 的作者)曾经公开说过,有计划淘汰掉现有的一套 Lua CFunction 接口。所以迟早你也会用上 lua-resty-core 所暴露的接口。这是其一。
其二,OpenResty 目前新的功能开发,都是放到 lua-resty-core 上的。毕竟旧的接口要淘汰了嘛。对 FFI 的偏好并不仅仅体现在新功能开发上。如果改用 FFI 能解决 CFunction 接口的 bug,OpenResty 开发者会认为这个问题已经解决了。(参见 BUG Report 严重(特别是使用了lua-resty-lock库的服务,有一定概率workers死锁,可重现))
最后,同样的方法,来自 lua-resty-core 的版本除了性能外,还会有其他优势。举个例子,因为内部实现上的差异,lua-resty-core 中的 ngx.re
可以用在 init_by_lua*
阶段,而原来的 Lua CFunction 版本不支持这么用。
为什么你应该在 OpenResty 项目中使用 lua-resty-core相关推荐
- 在C++项目中引入Lua(AlphaGo使用的方案)
最近大火的AlphaGo,其中的deepmind已经开源,可以到github中下载https://github.com/deepmind/lab·,网上还有一个基于Python开源AlphaGo,那个 ...
- 如何在多个项目中分离Asp.Net Core Mvc的Controller和Areas
前言 软件系统中总是希望做到松耦合,项目的组织形式也是一样,本篇文章将介绍在ASP.NET CORE MVC中怎么样将Controller与主网站项目进行分离,并且对Areas进行支持. 实践 1.新 ...
- Spring Redis中使用Lua脚本实现高并发原子操作
1. 前言 在上一文中我对 Lua 语言的一些简单的语法及其在 Redis 中的操作进行了介绍,但是在 Java 开发中我们还需要进一步的学习才能使这种技术落地.今天就结合Spring Data Re ...
- redis中使用lua脚本
一.概述 1.什么是lua脚本 Lua是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放. 其设计目的就是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能.因为广泛的应用于:游戏开 ...
- 在OR项目中使用火焰图
火焰图介绍 看到openresty里用火焰图觉得挺有意思的,又装逼又有用.它可以快速的定位到程序性能的瓶颈,生成程序的调用栈并且计算调用栈中每部分的 CPU 消耗,能够比较容易的找到CPU占用高.内存 ...
- 网关 Apache APISIX 在 360 基础运维平台项目中的实践
女主宣言 今天小编为大家分享一篇关于Apache APISIX的文章,文章从开发者的角度讲述了 Apache APISIX 网关在 360 基础运维平台的落地实践,希望能对大家有所帮助. PS:丰富的 ...
- canvas java 上传截图_在Vue项目中使用html2canvas生成页面截图并上传
使用方法 项目中引入 npm install html2canvas html代码 //html代码 js代码 // 引入html2canvas import html2canvas from 'ht ...
- android studio 自动提示jni代码,如何将JNI(C/C++本机代码)添加到现有的Android Studio项目中...
从现有项目中执行以下步骤: 1.修改build.gradle(模块应用程序)看起来像这样(很多变化!): apply plugin: 'com.android.model.application' m ...
- android使用webview上传文件,Android项目中如何在webview页面中上传文件
Android项目中如何在webview页面中上传文件 发布时间:2020-11-26 15:56:27 来源:亿速云 阅读:68 作者:Leah 本篇文章为大家展示了Android项目中如何在web ...
最新文章
- 以人为本的机器学习:谷歌人工智能产品设计概述 By 机器之心2017年7月17日 12:13 取代了手动编程,机器学习(ML)是一种帮助计算机发现数据中的模式和关系的科学。对于创建个人的和动态的经历
- linux线程(互斥锁、条件)
- [原]排错实战——拯救加载调试符号失败的IDA
- 我的Go+语言初体验——(1)超详细安装教程
- win10共享打印错误0x0000006_Win7打印机无法共享提示错误代码0x000006d9的解决方法...
- vue 表单 select option
- winscp怎么更改linux权限,Linux下,WinSCP普通用户登录sftp后切换到root权限 教程
- C语言之避免编译警告:unused用法(七)
- dial tcp 10.96.0.1:443: i/o timeout
- Win2019 ServerManager.exe 0xc0000135 应用程序错误
- 安全牛:安全与业务不存在平衡 证明价值是关键
- 摄像头、麦克风、耳麦免费在线检测(各种外设的在线检测网站)
- 杂记-Macbook Pro M1芯片能玩深度学习吗?
- 大连软件知名公司最新职位
- vim插件之pathogen,NERDTree,Command-T,Powerline
- 【全国大学生电子设计大赛】2021-04-26
- vi ~/.bashrc如何保存退出
- 江苏计算机学三本大学,2021年江苏三本大学最新排名及录取分数线
- 小程序多图上传云存储PHP
- mysql索引 实验报告_索引实验报告