这节课,我们来学习下 OpenResty 的另一块基石:LuaJIT。今天主要的篇幅,我会留给 Lua 和 LuaJIT 中重要和鲜为人知的一些知识点。而更多 Lua 语言的基础知识,你可以通过搜索引擎或者 Lua 的书籍自己来学习,这里我推荐 Lua 作者编写的《Lua 程序设计》这本书。

当然,在 OpenResty 中,写出正确的 LuaJIT 代码的门槛并不高,但要写出高效的 LuaJIT 代码绝非易事,这里的关键内容,我会在后面 OpenResty 性能优化部分详细介绍。

我们先来看下 LuaJIT 在 OpenResty 整体架构中的位置:

前面我们提到过,OpenResty 的 worker 进程都是 fork master 进程而得到的, 其实, master 进程中的 LuaJIT 虚拟机也会一起 fork 过来。在同一个 worker 内的所有协程,都会共享这个 LuaJIT 虚拟机,Lua 代码的执行也是在这个虚拟机中完成的。

这可以算是 OpenResty 的基本原理,后面课程我们再详细聊聊。今天我们先来理顺 Lua 和 LuaJIT 的关系。

标准 Lua 和 LuaJIT 的关系

先把重要的事情放在前面说:

标准 Lua 和 LuaJIT 是两回事儿,LuaJIT 只是兼容了 Lua 5.1 的语法。

标准 Lua 现在的最新版本是 5.3,LuaJIT 的最新版本则是 2.1.0-beta3。在 OpenResty 几年前的老版本中,编译的时候,你可以选择使用标准 Lua VM ,或者 LuaJIT VM 来作为执行环境,不过,现在已经去掉了对标准 Lua 的支持,只支持 LuaJIT。

LuaJIT 的语法兼容 Lua 5.1,并对 Lua 5.2 和 5.3 做了选择性支持。所以我们应该先学习 Lua 5.1 的语法,并在此基础上学习 LuaJIT 的特性。上节课我已经带你入门了 Lua 的基础语法,今天只提及 Lua 的一些特别之处。

值得注意的是,OpenResty 并没有直接使用 LuaJIT 官方提供的 2.1.0-beta3 版本,而是在此基础上,扩展了自己的 fork: [openresty-luajit2]:

OpenResty 维护了自己的 LuaJIT 分支,并扩展了很多独有的 API。

这些独有的 API,都是在实际开发 OpenResty 的过程中,出于性能方面的考虑而增加的。所以,我们后面提到的 LuaJIT,特指 OpenResty 自己维护的 LuaJIT 分支。

为什么选择 LuaJIT?

说了这么多 LuaJIT 和 Lua 的关系,你可能会纳闷儿,为什么不直接使用 Lua,而是要用自己维护的 LuaJIT 呢?其实,最主要的原因,还是 LuaJIT 的性能优势。

其实标准 Lua 出于性能考虑,也内置了虚拟机,所以 Lua 代码并不是直接被解释执行的,而是先由 Lua 编译器编译为字节码(Byte Code),然后再由 Lua 虚拟机执行。

而 LuaJIT 的运行时环境,除了一个汇编实现的 Lua 解释器外,还有一个可以直接生成机器代码的 JIT 编译器。开始的时候,LuaJIT 和标准 Lua 一样,Lua 代码被编译为字节码,字节码被 LuaJIT 的解释器解释执行。

但不同的是,LuaJIT 的解释器会在执行字节码的同时,记录一些运行时的统计信息,比如每个 Lua 函数调用入口的实际运行次数,还有每个 Lua 循环的实际执行次数。当这些次数超过某个随机的阈值时,便认为对应的 Lua 函数入口或者对应的 Lua 循环足够热,这时便会触发 JIT 编译器开始工作。

JIT 编译器会从热函数的入口或者热循环的某个位置开始,尝试编译对应的 Lua 代码路径。编译的过程,是把 LuaJIT 字节码先转换成 LuaJIT 自己定义的中间码(IR),然后再生成针对目标体系结构的机器码。

所以,所谓 LuaJIT 的性能优化,本质上就是让尽可能多的 Lua 代码可以被 JIT 编译器生成机器码,而不是回退到 Lua 解释器的解释执行模式。明白了这个道理,你才能理解后面学到的 OpenResty 性能优化的本质。

Lua 特别之处

正如我们上节课介绍的一样,Lua 语言相对简单。对于有其他开发语言背景的工程师来说,注意 到 Lua 中一些独特的地方后,你就能很容易的看懂代码逻辑。接下来,我们一起来看 Lua 语言比较特别的几个地方。

1. Lua 的下标从 1 开始

Lua 是我知道的唯一一个下标从 1 开始的编程语言。这一点,虽然对于非程序员背景的人来说更好理解,但却容易导致程序的 bug。

下面是一个例子:

$ resty -e 't={100}; ngx.say(t[0])'

你自然期望打印出 100,或者报错说下标 0 不存在。但结果出乎意料,什么都没有打印出来,也没有报错。既然如此,让我们加上 type 命令,来看下输出到底是什么:

$ resty -e 't={100};ngx.say(type(t[0]))'
nil

原来是空值。事实上,在 OpenResty 中,对于空值的判断和处理也是一个容易让人迷惑的点,后面我们讲到 OpenResty 的时候再细聊。

2. 使用 .. 来拼接字符串

这一点,上节课我也提到过。和大部分语言使用 + 不同,Lua 中使用两个点号来拼接字符串:

$ resty -e "ngx.say('hello' .. ', world')"
hello, world

在实际的项目开发中,我们一般都会使用多种开发语言,而 Lua 这种不走寻常路的设计,总是会让开发者的思维,在字符串拼接的时候卡顿一下,也是让人哭笑不得。

3. 只有 table 这一种数据结构

不同于 Python 这种内置数据结构丰富的语言,Lua 中只有一种数据结构,那就是 table,它里面可以包括数组和哈希表:

local color = {first = "red", "blue", third = "green", "yellow"}
print(color["first"])                 --> output: red
print(color[1])                         --> output: blue
print(color["third"])                --> output: green
print(color[2])                         --> output: yellow
print(color[3])                         --> output: nil

如果不显式地用_键值对_的方式赋值,table 就会默认用数字作为下标,从 1 开始。所以 color[1] 就是 blue。

另外,想在 table 中获取到正确长度,也是一件不容易的事情,我们来看下面这些例子:

local t1 = { 1, 2, 3 }
print("Test1 " .. table.getn(t1))local t2 = { 1, a = 2, 3 }
print("Test2 " .. table.getn(t2))local t3 = { 1, nil }
print("Test3 " .. table.getn(t3))local t4 = { 1, nil, 2 }
print("Test4 " .. table.getn(t4))

使用 resty 运行的结果如下:

Test1 3
Test2 2
Test3 1
Test4 1

你可以看到,除了第一个返回长度为 3 的测试案例外,后面的测试都是我们预期之外的结果。事实上,想要在 Lua 中获取 table 长度,必须注意到,只有在 table 是 _序列_ 的时候,才能返回正确的值。

那什么是序列呢?首先序列是数组(array)的子集,也就是说,table 中的元素都可以用正整数下标访问到,不存在键值对的情况。对应到上面的代码中,除了 t2 外,其他的 table 都是 array。

其次,序列中不包含空洞(hole),即 nil。综合这两点来看,上面的 table 中, t1 是一个序列,而 t3 和 t4 是 array,却不是序列(sequence)。

到这里,你可能还有一个疑问,为什么 t4 的长度会是 1 呢?其实这是因为,在遇到 nil 时,获取长度的逻辑就不继续往下运行,而是直接返回了。

不知道你完全看懂了吗?这部分确实相当复杂。那么有没有什么办法可以获取到我们想要的 table 长度呢?自然是有的,OpenResty 在这方面做了扩展,在后面专门的 table 章节我会讲到,这里先留一个悬念。

4. 默认是全局变量

我想先强调一点,除非你相当确定,否则在 Lua 中声明变量时,前面都要加上 local

local s = 'hello'

这是因为在 Lua 中,变量默认是全局的,会被放到名为 _G 的 table 中。不加 local 的变量会在全局表中查找,这是昂贵的操作。如果再加上一些变量名的拼写错误,就会造成难以定位的 bug。

所以,在 OpenResty 编程中,我强烈建议你总是使用 local 来声明变量,即使在 require module 的时候也是一样:

-- Recommended
local xxx = require('xxx')-- Avoid
require('xxx')

LuaJIT

明白了 Lua 这四点特别之处,我们继续来说 LuaJIT。除了兼容 Lua 5.1 的语法并支持 JIT 外,LuaJIT 还紧密结合了 FFI(Foreign Function Interface),可以让你直接在 Lua 代码中调用外部的 C 函数和使用 C 的数据结构。

下面是一个最简单的例子:

local ffi = require("ffi")
ffi.cdef[[
int printf(const char *fmt, ...);
]]
ffi.C.printf("Hello %s!", "world")

短短这几行代码,就可以直接在 Lua 中调用 C 的 printf 函数,打印出 Hello world!。你可以使用 resty 命令来运行它,看下是否成功。

类似的,我们可以用 FFI 来调用 NGINX、OpenSSL 的 C 函数,来完成更多的功能。实际上,FFI 方式比传统的 Lua/C API 方式的性能更优,这也是 lua-resty-core 项目存在的意义。下一节我们就来专门讲讲 FFI 和 lua-resty-core

此外,出于性能方面的考虑,LuaJIT 还扩展了 table 的相关函数:table.newtable.clear这是两个在性能优化方面非常重要的函数,在 OpenResty 的 lua-resty 库中会被频繁使用。不过,由于相关文档藏得非常深,而且没有示例代码,所以熟悉它们的开发者并不多。我们留到性能优化章节专门来讲它们。

写在最后

让我们来回顾下今天的内容。

OpenResty 出于性能的考虑,选择了 LuaJIT 而不是标准 Lua,并且维护了自己的 LuaJIT 分支。而 LuaJIT 基于 Lua 5.1 的语法,并选择性地兼容了部分 Lua5.2 和 Lua5.3 的语法,形成了自己的体系。至于你需要掌握的 Lua 语法,在下标、字符串拼接、数据结构和变量上,都有自己鲜明的特点,在写代码的时候你应该特别留意。

你在学习 Lua 和 LuaJIT 的时候,是否遇到一些陷阱和坑呢?欢迎留言一起来聊一聊,我在后面也专门写了一篇文章,来分享我遇到过的那些坑。也欢迎你把这篇文章分享给你的同事、朋友,一起学习,一起进步。

出自

温铭 -OpenResty从入门到实战 专栏

LuaJIT分支和标准Lua有什么不同?相关推荐

  1. 版本分支管理标准 - Trunk Based Development 主干开发模型

    之前分享过<版本分支管理标准 - Git Flow>,不过在实际使用过程中, 因为其有一定的复杂度,使用起来较为繁琐,所以一些人员较少的团队并不会使用这个方案. 在这基础上,一些新的分支管 ...

  2. Luajit作者给的Lua源码的阅读顺序

    推荐阅读顺序: lmathlib.c, lstrlib.c: 熟悉外部C API.不要纠结于模式匹配器.只是简单的功能. lapi.c: 查看API在内部的实现方式.只是浏览一下以获得对代码的感觉.根 ...

  3. OpenResty 概要及原理科普

    OpenResty® 是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库.第三方模块以及大多数的依赖项.用于方便地搭建能够处理超高并发.扩展性极高的动态 W ...

  4. openresty开发系列12--lua介绍及常用数据类型简介

    openresty开发系列12--lua介绍及常用数据类型简介 lua介绍   1993 年在巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de ...

  5. Lua和Luajit

    一.什么是lua&luaJit lua(www.lua.org)其实就是为了嵌入其它应用程序而开发的一个脚本语言, luajit(www.luajit.org)是lua的一个Just-In-T ...

  6. lua 区间比较_TI-Lua 系列教程2.4.1: 条件分支

    2.4.1 条件分支 数学上,分段函数在不同的区间,可以有不同的表达式.这些不同的表达式,可以视为分段函数的分支. 要想在程序中实现这样的一个分段函数,就需要使用分支结构,来实现不同区间对输入的不同计 ...

  7. Lua和Luajit的优势和不足(1)

    一.什么是lua&luaJit lua(www.lua.org)其实就是为了嵌入其它应用程序而开发的一个脚本语言,luajit(www.luajit.org)是lua的一个Just-In-Ti ...

  8. 用lua扩展你的Nginx(写的非常好)

    一. 概述 Nginx是一个高性能,支持高并发的,轻量级的web服务器.目前,Apache依然web服务器中的老大,但是在全球前1000大的web服务器中,Nginx的份额为22.4%.Nginx采用 ...

  9. 用lua扩展你的Nginx(整理)

    首先得声明.这不是我的原创,是在网上搜索到的一篇文章,原著是谁也搞不清楚了.按风格应该是属于章亦春的文章. 整理花了不少时间,所以就暂写成原创吧. 一. 概述 Nginx是一个高性能.支持高并发的,轻 ...

最新文章

  1. 《职场》笔记20061119
  2. 关于mysql-connector-net在C#中的用法
  3. 微信小程序 调用地图接口,实现定位
  4. Tensorflow CNN(两层卷积+全连接+softmax)
  5. 10. 我的第一个Java应用程序
  6. 力扣-590. N 叉树的后序遍历
  7. 构建Docker镜像仓库的另一选择:Nexus3 - DockOne.io
  8. C#中的overload,overwrite,override的语义区别
  9. 平面变压器的设计(翻译)(3)
  10. 51单片机-串行口通信实验
  11. ubuntu 18.04 英伟达显卡驱动
  12. python运算符重载、并且编写复数类的加减乘除_编程基础篇:定义一个复数类Complex,重载运算符“+,-,*,/”,使之能用于复数的加减乘除.....出现的问题及代码...
  13. 奔波真是辛苦啊,然而生命终将逝去,只希望当一切都结束的时候,能够没有遗憾吧。
  14. grasp设计模式应用场景_设计模式 GRASP GoF
  15. 安装pytorch报错torch.cuda.is_available()=false的解决方法
  16. QT4.8.6调用zlib库实现数据流的压缩与解压缩
  17. python读取excel写入数据库_python读取Excel内容并写入MySQL数据库脚本
  18. 第二证券|监管层紧盯内幕交易 市场生态持续改善
  19. VS.net 2005 MFC QQ 2006 TM 2006 消息发送 简单核心代码
  20. 如何使用晨曦记账本,记录借还款

热门文章

  1. android像360一样跳转到系统菜单,Android开源库-仿360手机助手底部动画菜单布局
  2. Northwind中文版Access2000、MSSQL版也适用LinqPad学习
  3. 1177_SICP学习笔记_嵌套映射
  4. matlab仿真电气连接,电气系统模块库-simulink与电气系统接口
  5. VS Code 之 Semantic Highlighing
  6. 金算盘与金蝶的“全程电子商务“谁第一?
  7. 【设计理念】Android UI
  8. WINSOFT ComPort轻松连接到各种串行端口和连接设备
  9. JavaScript入门到精通(五)连载
  10. Winform版微博管理工具