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:

Projects using LuaJIT FFI:


  1. When using Luajit, is it better to use FFI or normal lua bindings? ↩︎

  2. In LuaJIT FFI/C++ binding, best approach?, someone decided to use clang to parse each method and generate C wrappers. Others do it manually. ↩︎

September 1, 2013
57fed1c — March 15, 2024