1. FFI 教程

原文: FFI Tutorial

相关链接:OpenResty 最佳实践之 FFI

加载 FFI 库

FFI 库时默认编译进 LuaJIT 中的,但是不会默认加载或初始化。因此,当需要使用 FFI 库时,需要在 Lua 文件的开头添加如下语句:

local ffi = require("ffi")

访问标准系统函数

如下示例显示了如何访问标准系统函数。

local ffi = require("ffi")
ffi.cdef[[
void Sleep(int ms);
int poll(struct pollfd *fds, unsigned long nfds, int timeout);
]] local sleep
if ffi.os == "Windows" then
function sleep(s)
ffi.C.Sleep(s*1000)
end
else
function sleep(s)
ffi.C.poll(nil, 0, s*1000)
end
end for i = 1, 160 do
io.write("."); io.flush()
sleep(0.01)
end
io.write("\n")

访问 zlib 压缩库

如下示例显示了如果在 Lua 代码中访问 zlib 压缩库。

local ffi = require("ffi")
-- 定义由 zlib 提供的 C 函数
ffi.cdef[[
unsigned long compressBound(unsigned long sourceLen);
int compress2(uint8_t *dest, unsigned long *destLen,
const uint8_t *source, unsigned long sourceLen, int level);
int uncompress(uint8_t *dest, unsigned long *destLen,
const uint8_t *source, unsigned long sourceLen);
]]
-- 加载 zlib 共享库。在 POSIX 系统上,名为 libz.so,通常是预安装的。
-- 因为 ffi.load() 会自动添加缺失的标准前缀/后缀,因此可以简单地加载 "z" 库。
local zlib = ffi.load(ffi.os == "Windows" and "zlib1" or "z") local function compress(txt)
-- 首先,通过使用未压缩字符串的长度来调用 zlib.compressBoud 来获取
-- 压缩缓存区的最大大小.
local n = zlib.compressBound(#txt)
-- 分配这个 n 大小的字节缓存区,类型规范中的 [?] 表示可变长度数组(VLA).
-- 该数组的实际元素个数由 ffi.new 的第二个参数给出.
local buf = ffi.new("uint8_t[?]", n)
-- 看上面 compress2 的函数声明可知,destLen 被定义为一个指针。这是因为
-- 传入的是最大缓存区的大小并返回实际使用的长度.
-- 在 C 中可以通过传入一个本地变量的地址 (即 &buflen),但是在 Lua 中没有
-- 地址操作,因此传入的是只有一个元素的数组。
local buflen = ffi.new("unsigned long[1]", n)
local res = zlib.compress2(buf, buflen, txt, #txt, 9)
assert(res == 0)
-- 将压缩数据作为 Lua 字符串返回,因此使用 ffi.string(),它需要指向
-- 数据开头和实际长度的指针,这个长度已经通过 buflen 数组返回了
return ffi.string(buf, buflen[0])
end local function uncompress(comp, n)
local buf = ffi.new("uint8_t[?]", n)
local buflen = ffi.new("unsigned long[1]", n)
local res = zlib.uncompress(buf, buflen, comp, #comp)
assert(res == 0)
return ffi.string(buf, buflen[0])
end -- Simple test code.
local txt = string.rep("abcd", 1000)
print("Uncompressed size: ", #txt)
local c = compress(txt)
print("Compressed size: ", #c)
local txt2 = uncompress(c, #txt)
assert(txt2 == txt)

为 C Type 定义 Metamethods

local ffi = require("ffi")
ffi.cdef[[
typedef struct { double x, y; } point_t;
]] local point
local mt = {
__add = function(a, b) return point(a.x+b.x, a.y+b.y) end,
__len = function(a) return math.sqrt(a.x*a.x + a.y*a.y) end,
__index = {
area = function(a) return a.x*a.x + a.y*a.y end,
},
}
point = ffi.metatype("point_t", mt) local a = point(3, 4)
print(a.x, a.y) --> 3 4
print(#a) --> 5
print(a:area()) -- 25
local b = a + point(0.5, 8)
print(#b) --> 12.5

C 和 LuaJIT 相互转化

如下列表显示了如何将常见的 C 语言转化为 LuaJIT FFI:

缓存或不缓存

将库函数缓存在 local 变量或 upvalues 中是一种常见的用法,如下示例

local byte, char = string.byte, string.char
local function foo(x)
return char(byte(x) + 1)
end

这个可以通过(更快的)直接使用 local 变量或 upvalue 来替换多次哈希表查找。这对于 LuaJIT 来说不是那么重要,因为 JIT 编译器大量优化哈希表查找,甚至能将大部分内容从内循环中提升出来。但是它并不能消除所有这些。

通过 FFI 库调用 C 函数有一点不同。JIT 编译器有特殊的逻辑来消除从 C 库命名空间中解析的函数的所有查找开销。因此,缓存单个 C 函数是没有用的,实际上是适得其反:

local funca, funcb = ffi.funca, ffi.C.funcb -- Not helpful
local function foo(x, n)
for i = 1, n do funcb(funca(x, i), 1) end
end

这会将它们变成间接调用,并生成更大更慢的机器代码。相反,需要缓存的是命令空间本身并依赖 JIT 编译器来消除查找:

local C = ffi.C         -- Instead use this
local function foo(x, n)
for i = 1, n do C.funcb(C.funca(x, i), 1) end
end

这会生成更短更快的代码。因此不要缓存 C 函数,但要缓存命名空间。大多数情况下,命名空间已经位于外部作用域的本地变量中。如来自 local lib = ffi.load(...)。注意,不需要将其复制到函数范围的本地变量中。

2. ffi.* API

词汇表

  • cdecl:抽象 C 类型声明(Lua 字符串)。
  • ctype:C 类型对象。由 ffi.typeof() 返回的一种特殊的 cdata,当被调用时是作为 cdata 的构造函数。
  • ct:一种类型规范,可用于大多数 API 函数。cdecl,ctype 或 cdata 作为模板类型。
  • cb:一个回调对象。这是一个包含特殊函数指针的 C 数据对象。从 C 代码调用此函数会运行关联的 Lua 函数。
  • VLA:通过 [?] 代替元素个数值声明的一个可变长度数组,如 "int[?]"。当创建的时候必须给出元素个数。
  • VLS:可变长度结构体是一个 C 类型的结构体,最后一个元素是 VLA。适用于声明和创建的相同规则。

2.1 声明和访问外部符号

必须首先声明外部符号,然后可以通过索引 C 库命名空间来访问外部符号,该命名空间自动将符号绑定到特定库。

2.1.1 ffi.cdef(def)

声明 C 函数或者 C 的数据结构,数据结构可以是结构体、枚举或者是联合体,函数可以是 C 标准函数,或者第三方库函数,也可以是自定义的函数,注意这里只是函数的声明,并不是函数的定义。声明的函数应该要和原来的函数保持一致。

ffi.cdef[[
typedef struct foo { int a, b; } foo_t; /* Declare a struct and typedef. */
int dofoo(foo_t *f, int n); /* Declare an external C function */
]]

注意,外部符号仅被声明,但它们并不受任何特定地址的约束。使用 C 库命名空间实现绑定.此外所有使用的库函数都要对其进行声明。

如何使用自定义的函数?

如下示例,创建一个 myffi.c,内容:

int add(int x, int y)
{
return x + y;
}

接着在 Linux 下生成动态链接库:

gcc -g -o libmyffi.so -fpic -shared myffi.c

在 LD_LIBRARY_PATH 环境变量中添加生成库的路径:

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:your_lib_path

在 Lua 代码中增加如下行:

ffi.load(name, [,global])

ffi.load 会通过给定的 name 加载动态库,返回一个绑定到这个库符号的新的 C 库命名空间,在 POSIX 系统中,如果 global 被设置为 true,这个库符号被加载到一个全局命名空间。另外这个 name 可以是一个动态库的路径,那么会根据路径来查找,否则的话会在默认的搜索路径中去找动态库。在 POSIX 系统中,如果在 name 这个字段中没有写上点符号 .,那么 .so 将会被自动添加进去,例如 ffi.load("z") 会在默认的共享库搜寻路径中去查找 libz.so,在 windows 系统,如果没有包含点号,那么 .dll 会被自动加上。

local ffi = require("ffi")
local myffi = ffi.load("myffi") ffi.cdef[[
int add(int x, int y); /* don't forget to declare */
]] local res = myffi.add(1, 2)
print(res) -- output: 3 Note: please use luajit to run this script.

此外,可以使用 ffi.C (调用 ffi.cdef 中声明的系统函数)来直接调用 add 函数(注:要在 ffi.load 中加上参数 true,如 ffi.load('myffi', true))。

local ffi = require"ffi"
ffi.load('myffi', true) ffi.cdef[[
int add(int x, int y); /* don't forget to declare */
]] local res = ffi.C.add(1, 2)
print(res) -- output: 3 Note: please use luajit to run this script.

2.1.2 ffi.C

这是默认的 C 库命名空间--注意为大写的 C。它绑定到目标系统上的默认符号集或库。这些或多或少与 C 编译器默认提供的相同,而不指定额外的链接库。

在 POSIX 系统中,它绑定到默认或全局命名空间中的符号。这包括可执行文件中的所有导出符号以及加载到全局命名空间中的任意库。这至少包括 libc,libm,libdl(在 Linux 中),libgcc(如果使用 GCC 编译器),以及 LuaJIT 本身提供的 Lua/C API 中的任何导出符号。

2.1.3 clib = ffi.load(name [, global])

这将加载由 name 指定的动态库,并返回一个绑定到其符号的新 C 库命名空间。在 POSIX 系统中,如果 global 为 true,这个库的符号将会加载到全局命名空间中。

如果 name 是路径,该库将会从该路径中加载。否则,name 将以与系统相关的方式进行规范化,并按默认搜索路径来搜索动态库:在 POSIX 系统上,如果 name 不包含 '.',则追加扩展名 .so。此外,如果需要,还会添加库的前缀。所以 ffi.load("z") 在默认的共享库路径中搜索 "libz.so"。

2.2 创建 cdata 对象

2.2.1 ffi.typeof

ctype = ffi.typeof(ct)

创建一个 ctype 对象,会解析一个抽象的 C 类型定义。该函数仅用于解析 cdecl 一次,然后使用生成的 ctype 对象作为构造函数。

local uintptr_t = ffi.typeof("uintptr_t")
local c_str_t = ffi.typeof("const char*")
local int_t = ffi.typeof("int")
local int_array_t = ffi.typeof("int[?]")

2.2.2 ffi.new

如下 API 函数创建 cdata 对象(ctype() 返回 "cdata")。所有创建的对象都是垃圾回收的。

cdata = ffi.new(ct [,nelem] [,init...])
cdata = ctype([nelem,] [init...])

ffi.new 开辟空间,第一个参数为 ctype 对象,ctype 对象最好通过 ctype = ffi.typeof(ct) 构建。

ffi.new 和 ffi.C.malloc 的区别?

如果使用 ffi.new 分配的 cdata 对象指向的内存块是由垃圾回收器 LuaJIT GC 自动管理的,所有不需要用户去释放内存。

如果使用 ffi.C.malloc 分配的空间便不再使用 LuaJIT 自己的分配器了,所以不是由 LuaJIT GC 来管理的,但是,要注意的是 ffi.C.malloc 返回的指针本身所对应的 cdata 对象还是由 LuaJIT GC 来管理的,也就是这个指针的 cdata 对象指向的是用 ffi.C.malloc 分配的内存空间。这个时候,你应该通过 ffi.gc() 函数在这个指针的 cdata 对象上面注册自己的析构函数,这个析构函数里面可以再调用 ffi.C.free,这样的话当 C 指针所对应的 cdata 对象被 LuaJIT GC 管理器垃圾回收的时候,也会自动调用你注册的那个析构函数来执行 C 级别的内存释放。

请尽可能使用最新版本的 LuaJIT,x86_64 上由 LuaJIT GC 管理的内存已经由 1G->2G,虽然管理的内存变大了,但是如果要使用很大的内存,还是用 ffi.C.malloc 来分配会比较好,避免耗尽了 LuaJIT GC 管理内存的上限。

local int_array_t = ffi.typeof("int[?]")
local bucket_v = ffi.new(int_array_t, bucket_sz) local queue_arr_type = ffi.typeof("lrucache_pureffi_queue_t[?]")
local q = ffi.new(queue_arr_type, size + 1)

2.2.3 ffi.cast

cdata = ffi.cast(ct, init)

创建一个 scalar cdata 对象。

local c_str_t = ffi.typeof("const char*")
local c_str = ffi.case(c_str_t, str) -- 转换为指针地址 local uintptr_t ffi.typeof("uintptr_t")
tonumber(ffi.cast(uintptr_t, c_str) -- 转换为数字

2.2.4 ffi.metatype

ctype = ffi.metatype(ct, metatable)

为给定的 ct 创建一个 ctype 对象,并将其与 metatable 相关联。仅允许使用 struct/union 类型,复数和向量。如果需要,其他类型可以封装在 struct 中。

与 metatable 的关联是永久性的,之后不可更改。之后,metatable 的内容和 __index 表(如果有的话)的内容都不能被修改。无论对象如何创建或源自何处,相关地元表都会自动应用于此类型的所有用途。注意,对类型的预定义操作具有优先权(如,声明的字段名称不能被覆盖)。

LuaJIT 之 FFI的更多相关文章

  1. luajit利用ffi结合C语言实现面向对象的封装库

    luajit中.利用ffi能够嵌入C.眼下luajit的最新版是2.0.4,在这之前的版本号我还不清楚这个扩展库详细怎么样,只是在2.04中,真的非常爽.  既然是嵌入C代码.那么要说让lua支持 ...

  2. Atitit ABI FFI 的区别与联系 attilax总结

    Atitit ABI FFI 的区别与联系 attilax总结 FFI stands for Foreign Function Interface. A foreign function interf ...

  3. luajit官方性能优化指南和注解

    luajit是目前最快的脚本语言之一,不过深入使用就很快会发现,要把这个语言用到像宣称那样高性能,并不是那么容易.实际使用的时候往往会发现,刚开始写的一些小test case性能非常好,经常毫秒级就算 ...

  4. 用好lua+unity,让性能飞起来——luajit集成篇/平台相关篇

    luajit集成篇 大家都知道luajit比原生lua快,快在jit这三个字上. 但实际情况是,luajit的行为十分复杂.尤其jit并不是一个简单的把代码翻译成机器码的机制,背后有很多会影响性能的因 ...

  5. [转]Lua和Lua JIT及优化指南

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

  6. 【实战分享】又拍云 OpenResty / Nginx 服务优化实践

    2018 年 11 月 17 日,由 OpenResty 主办的 OpenResty Con 2018 在杭州举行.本次 OpenResty Con 的主题涉及 OpenResty 的新开源特性.业界 ...

  7. 使用MongoDB血泪般的经验教训

    故事背景,天书世界,现在项目已经属于成熟维护期,是时候总结一下当时的想法 第一个问题,为什么使用mongodb? 数据库对于游戏项目本身的要求与传统业务系统差异较大,所以nosql的弱结构性对于我那是 ...

  8. OpenResty 最佳实践 (2)

    此文已由作者汤晓静授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. lua 协程与 nginx 事件机制结合 文章前部分用大量篇幅阐述了 lua 和 nginx 的相关知识,包 ...

  9. 转:TensorFlow和Caffe、MXNet、Keras等其他深度学习框架的对比

    http://geek.csdn.net/news/detail/138968 Google近日发布了TensorFlow 1.0候选版,这第一个稳定版将是深度学习框架发展中的里程碑的一步.自Tens ...

随机推荐

  1. 一种电平转换的方法,使用CPLD

    参考应用笔记 http://www.doc88.com/p-0197252336968.html 前言 在原理图设计初期,可能涉及到引脚电平的转换操作,比如主FPGA的某BANK电平为1.5V,但外围 ...

  2. php中的特殊标签

    参考:https://www.freebuf.com/column/212586.html 今天看到这篇文章讲到了ctf中的一些关于php标签的小姿势,我虽然不打ctf,但是平常做php的代码审计也经 ...

  3. HTML5 使用localstorage 本地存储

    HTML 本地存储介绍 最早的 Cookies 自然是大家都知道,问题主要就是太小,大概也就 4KB 的样子,而且 IE6 只支持每个域名20个cookies,太少了.优势就是大家都支持,而且支持得还 ...

  4. linux 安装mysql(rpm文件安装)

    三 卸载旧版本的MySql (没有的话,则跳过此步骤)       1.查看旧版本MySql       rpm -qa | grep mysql       将会列出旧版本MySql的组件列表,如: ...

  5. 【ogg三】日常运维篇:清理归档日志,ogg进程注册服务,定期备份数据库

    清理归档日志 ogg使用需要开启归档日志,归档日志会随着时间的推移逐渐增多,占满空间,导致应用无法正常运行. 如果归档日志满了会报错 ORA-00257:archiver error解决办法 检查fl ...

  6. kvm虚拟机在线调整硬件配置

    #centos5.x版本不支持动态调整内存,CPU,以下是在centos6.x上测试 1.查看虚拟机信息 shell> virsh dumpxml cos_v1 | head -n 10 < ...

  7. Linux命令——getent

    简介 getent命令帮助用户administrative databases中查找相关信息.administrative databases包括: passwd – can be used to c ...

  8. 河南省acm第九届省赛--《表达式求值》--栈和后缀表达式的变形--手速题

    表达式求值 时间限制:1000 ms | 内存限制:65535 KB 难度:3   描述 假设表达式定义为:1. 一个十进制的正整数 X 是一个表达式.2. 如果 X 和 Y 是 表达式,则 X+Y, ...

  9. 微信小程序导入Vant报错

    作者:如也_d1c0链接:https://www.jianshu.com/p/0d2332984f8c来源:简书简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处. 先放出来Vant ...

  10. STM32F10XX学习笔记的石墨连接

    https://shimo.im/docs/QHGRrWxbeb0NiBm9/ <STM32F10X系列笔记>,可复制链接后用石墨文档 App 打开