Lua
LuaJIT has an FFI which allows code to be JIT compiled, whereas regular C API calls aren’t 1:
Pros:
- Speed
- Easier bindings done from the Lua side, given a simple C ABI of whatever needs binding. No unexpected code being generated that may present edge-case bugs which might be difficult to detect.
Cons:
- Safety. Third-party scripting can easily crash the application
- Bindings seem to be simpler from a C-language perspective. To this end, C++ programs would presumably have to write C ABI functions to be picked up/wrapped on Lua’s end via the FFI 2. This contrasts with automatically generated SWIG bindings from C++ to Lua’s C API. However, I think a benefit is that it ends up being cleaner and more precise/predictable than generated code.
Using FFI
Using the FFI in LuaJIT is pretty straightforward. A shared library has to be created that exposes the C functions:
extern "C" {
DLLEXPORT void some_function();
DLLEXPORT int get_count();
}
Where DLLEXPORT
is __declspec(dllexport)
on Windows. This can then be loaded by a Lua script:
local ffi = require('ffi')
ffi.cdef[[
void some_function();
int get_count();
]]
local api = ffi.load("thelibrary")
api.some_function();
local count = api.get_count();
Official FFI Resources
C wrapper development resources:
Callbacks
There is a heavy overhead with C-to-Lua calls, generally found in the form of callbacks in which a C FFI function is provided a callback function written in Lua. The overhead is seemingly similar to the C API calls.
Do not use callbacks for performance-sensitive work: e.g. consider a numerical integration routine which takes a user-defined function to integrate over. It’s a bad idea to call a user-defined Lua function from C code millions of times. The callback overhead will be absolutely detrimental for performance.
It’s considerably faster to write the numerical integration routine itself in Lua — the JIT compiler will be able to inline the user-defined function and optimize it together with its calling context, with very competitive performance.
Instead of passing a callback to a C function that applies that function to a sequence, a C function should be created that generates/yields the sequence an element at a time, with the processing done in Lua:
For new designs avoid push-style APIs: a C function repeatedly calling a callback for each result. Instead use pull-style APIs: call a C function repeatedly to get a new result. Calls from Lua to C via the FFI are much faster than the other way round. Most well-designed libraries already use pull-style APIs (read/write, get/put).
Here is a benchmark of the forms of callbacks.
Resources
Resources about the language:
Resources on LuaJIT:
- Scripting with LuaJIT and selectively sandboxing the FFI
- FFI Embedding Talk #1
- FFI Embedding Talk #2
Projects using LuaJIT FFI:
-
When using Luajit, is it better to use FFI or normal lua bindings? ↩︎
-
In LuaJIT FFI/C++ binding, best approach?, someone decided to use clang to parse each method and generate C wrappers. Others do it manually. ↩︎