139 lines
6.9 KiB
Markdown
139 lines
6.9 KiB
Markdown
# FFI Library
|
||
|
||
The FFI library allows **calling external C functions** and **using C data structures** from pure Lua code.
|
||
|
||
The FFI library largely obviates the need to write tedious manual Lua/C bindings in C. No need to learn a separate binding language — **it parses plain C declarations!** These can be cut-n-pasted from C header files or reference manuals. It's up to the task of binding large libraries without the need for dealing with fragile binding generators.
|
||
|
||
The FFI library is tightly integrated into LuaJIT (it's not available as a separate module). The code generated by the JIT-compiler for accesses to C data structures from Lua code is on par with the code a C compiler would generate. Calls to C functions can be inlined in JIT-compiled code, unlike calls to functions bound via the classic Lua/C API.
|
||
|
||
This page gives a short introduction to the usage of the FFI library. *Please use the FFI sub-topics in the navigation bar to learn more.*
|
||
|
||
## Motivating Example: Calling External C Functions
|
||
|
||
It's really easy to call an external C library function:
|
||
|
||
```lua
|
||
local ffi = require "ffi"; -- (1)
|
||
ffi.cdef [[
|
||
int printf(const char *fmt, ...);
|
||
]]; -- (2)
|
||
ffi.C.printf("Hello %s!", "world"); -- (3)
|
||
```
|
||
|
||
So, let's pick that apart:
|
||
|
||
1. Load the FFI library.
|
||
2. Add a C declaration for the function. The
|
||
part inside the double-brackets (in green) is just standard C syntax.
|
||
3. Call the named C function — Yes, it's that
|
||
simple!
|
||
|
||
Actually, what goes on behind the scenes is far from simple: (3) makes use of the standard C library namespace `ffi.C`. Indexing this namespace with a symbol name (`"printf"`) automatically binds it to the standard C library. The result is a special kind of object which, when called, runs the `printf` function. The arguments passed to this function are automatically converted from Lua objects to the corresponding C types.
|
||
|
||
Ok, so maybe the use of `printf()` wasn't such a spectacular example. You could have done that with `io.write()` and `string.format()`, too. But you get the idea ...
|
||
|
||
So here's something to pop up a message box on Windows:
|
||
|
||
```lua
|
||
local ffi = require "ffi";
|
||
ffi.cdef [[
|
||
int MessageBoxA(void *w, const char *txt, const char *cap, int type);
|
||
]];
|
||
ffi.C.MessageBoxA(nil, "Hello world!", "Test", 0);
|
||
```
|
||
|
||
Bing! Again, that was far too easy, no?
|
||
|
||
Compare this with the effort required to bind that function using the classic Lua/C API: create an extra C file, add a C function that retrieves and checks the argument types passed from Lua and calls the actual C function, add a list of module functions and their names, add a `luaopen_*` function and register all module functions, compile and link it into a shared library (DLL), move it to the proper path, add Lua code that loads the module aaaand ... finally call the binding function. Phew!
|
||
|
||
## Motivating Example: Using C Data Structures
|
||
|
||
The FFI library allows you to create and access C data structures. Of course, the main use for this is for interfacing with C functions. But they can be used stand-alone, too.
|
||
|
||
Lua is built upon high-level data types. They are flexible, extensible and dynamic. That's why we all love Lua so much. Alas, this can be inefficient for certain tasks, where you'd really want a low-level data type. E.g. a large array of a fixed structure needs to be implemented with a big table holding lots of tiny tables. This imposes both a substantial memory overhead as well as a performance overhead.
|
||
|
||
Here's a sketch of a library that operates on color images, plus a simple benchmark. First, the plain Lua version:
|
||
|
||
```lua
|
||
local floor = math.floor;
|
||
|
||
local function image_ramp_green(n)
|
||
local img = {};
|
||
local f = 255 / (n - 1);
|
||
|
||
for i = 1, n do
|
||
img[i] = { r = 0, g = floor((i - 1) * f), b = 0, a = 255 };
|
||
end
|
||
|
||
return img;
|
||
end
|
||
|
||
local function image_to_gray(img, n)
|
||
for i = 1, n do
|
||
local val = floor(0.3 * img[i].red + 0.59 * img[i].green + 0.11 * img[i].blue);
|
||
img[i].r = val;
|
||
img[i].g = val;
|
||
img[i].b = val;
|
||
end
|
||
end
|
||
|
||
local N = 400 * 400;
|
||
local img = image_ramp_green(N);
|
||
|
||
for i = 1, 1000 do
|
||
image_to_gray(img, N);
|
||
end
|
||
```
|
||
|
||
This creates a table with 160.000 pixels, each of which is a table holding four number values in the range of 0-255. First, an image with a green ramp is created (1D for simplicity), then the image is converted to grayscale 1000 times. Yes, that's silly, but I was in need of a simple example ...
|
||
|
||
And here's the FFI version. The modified parts have been marked:
|
||
|
||
```lua
|
||
local ffi = require "ffi"; -- (1)
|
||
ffi.cdef [[
|
||
typedef struct { uint8_t r, g, b, a; } rgba_pixel;
|
||
]];
|
||
|
||
local function image_ramp_green(n)
|
||
local img = ffi.new("rgba_pixel[?]", n); -- (2)
|
||
local f = 255 / (n - 1);
|
||
|
||
for i = 0, n - 1 do -- (3)
|
||
img[i].g = i * f; -- (4)
|
||
img[i].a = 255;
|
||
end
|
||
|
||
return img;
|
||
end
|
||
|
||
local function image_to_grey(img, n)
|
||
for i = 0, n - 1 do -- (3)
|
||
local val = 0.3 * img[i].r + 0.59 * img[i].g + 0.11 * img[i].b; -- (5)
|
||
img[i].r = val; img[i].g = val; img[i].b = val
|
||
end
|
||
end
|
||
|
||
local N = 400 * 400;
|
||
local img = image_ramp_green(N);
|
||
for i = 1, 1000 do
|
||
image_to_grey(img, N);
|
||
end
|
||
```
|
||
|
||
Ok, so that wasn't too difficult:
|
||
|
||
1. First, load the FFI library and declare the low-level data type. Here we choose a `struct` which holds four byte fields, one for each component of a 4x8 bit RGBA pixel.
|
||
2. Creating the data structure with `ffi.new()` is straightforward — the `'?'` is a placeholder for the number of elements of a variable-length array.
|
||
3. C arrays are zero-based, so the indexes have to run from `0` to `n-1`. One might want to allocate one more element instead to simplify converting legacy code.
|
||
4. Since `ffi.new()` zero-fills the array by default, we only need to set the green and the alpha fields.
|
||
5. The calls to `math.floor()` can be omitted here, because floating-point numbers are already truncated towards zero when converting them to an integer. This happens implicitly when the number is stored in the fields of each pixel.
|
||
|
||
Now let's have a look at the impact of the changes: first, memory consumption for the image is down from 22 Megabytes to 640 Kilobytes (400 * 400 * 4 bytes). That's a factor of 35x less! So, yes, tables do have a noticeable overhead. BTW: The original program would consume 40 Megabytes in plain Lua (on x64).
|
||
|
||
Next, performance: the pure Lua version runs in 9.57 seconds (52.9 seconds with the Lua interpreter) and the FFI version runs in 0.48 seconds on my machine (YMMV). That's a factor of 20x faster (110x faster than the Lua interpreter).
|
||
|
||
The avid reader may notice that converting the pure Lua version over to use array indexes for the colors (`[1]` instead of `.red`, `[2]` instead of `.green` etc.) ought to be more compact and faster. This is certainly true (by a factor of ~1.7x). Switching to a struct-of-arrays would help, too.
|
||
|
||
However, the resulting code would be less idiomatic and rather error-prone. And it still doesn't get even close to the performance of the FFI version of the code. Also, high-level data structures cannot be easily passed to other C functions, especially I/O functions, without undue conversion penalties.
|