Hacker News
Performance improvements in libffi
Koromix
|next
[-]
Benchmarks are here: https://koffi.dev/benchmarks
It's still a little different because in my case, the instructions tend to do two things: decode JS value and prepare register/stack. For typical functions, only a few instructions have to run, with minimal overhead. So, for example, I have a PushBool instruction which calls napi_get_value_bool() and then puts the bool at the correct offset (pre-computed) so that it ends up in a register or on the stack.
A function like int atoi(const char *) ends up with only two bytecode instructions:
- PushString
- RunInt32 (combined macro-operation that defers to assembly to set up registers, call the function, and then directly decodes the value)
Or another exemple, void *memset(void *ptr, int value, size_t size) only needs four instructions: - PushPointer
- PushInt32
- PushUInt64
- RunPointer
I've coupled that with a tail-call direct-threaded interpreter, with Clang's __attribute__((preserve_none)) ABI, just like Python did recently: https://github.com/python/cpython/issues/128563
quotemstr
|next
|previous
[-]
Notably, the COM bytecode covers not only procedure-level argument-passing, but data structure transformations themselves. It's a nice setup.
menaerus
|next
|previous
[-]
rurban
|next
|previous
[-]
atgreen
|root
|parent
[-]
Something I should have mentioned is that we could have avoided the new APIs if only there was space in the ffi_cif to stash a plan pointer. And I didn't want to break ABIs for this.
tadfisher
|previous
[-]
atgreen
|root
|parent
|next
[-]
quotemstr
|root
|parent
|previous
[-]
Keep in mind that optimizing the call doesn't optimize the marshaling: even with an AOT-compiled FFI trampoline, if you're, say, sending a string from one place to another, you usually need to transform the string in some manner (copy it, change encoding, add/remove length prefixes, etc.) and JITing the libffi parameter passing won't help you do the string stuff any faster.
In fact, trying to AOT the connections can make your program worse, both by bloating it (causing some likely small, but still, cache pressure) and by complicating your build and deployment process.
libffi bytecode is good. I wouldn't bother with native code until I had a profile in hand showing the bytecode to be the bottleneck, and even then, I'd check it a three or four times to make sure I didn't get the profiling wrong. FFI is just seldom the problem in real-world systems.