Speed is the reason I wrote infix and Affix and I think I've achieved a good balance of features vs. speed. I've done a lot of work to reduce overhead on the hot path but what if we could go... faster?
Standard Affix calls involve a "marshalling" phase: Perl values are converted to C values, stored in a buffer, passed to the JIT trampoline, and then passed to the C function. This is flexible, but adds overhead. Direct marshalling removes the middleman; Affix generates a specialized JIT trampoline that knows how to read your Perl variables directly from SV*.
The Recipe
We will compare the standard wrap against direct_wrap using a simple addition function.
use v5.40;
use Affix qw[:all];
use Affix::Build;
use Benchmark qw[cmpthese];
# 1. Compile
my $c = Affix::Build->new();
$c->add( \<<~'END', lang => 'c' );
int add(int a, int b) { return a + b; }
END
my $lib = $c->link;
# 2. Standard Bind
my $std_add = wrap $lib, 'add', [ Int, Int ] => Int;
# 3. Direct Bind
# The syntax is identical, just a different function name.
my $fast_add = direct_wrap $lib, 'add', [ Int, Int ] => Int;
# 4. Benchmark
cmpthese(
-5,
{ Standard => sub { $std_add->( 10, 20 ) },
Direct => sub { $fast_add->( 10, 20 ) }
}
);
The Results
On my machine, direct marshalling can be faster for simple primitives.
Rate Standard Direct
Standard 21102786/s -- -9%
Direct 23192942/s 10% --
The difference between the two grows as the number of parameters grows. With a C function with just one more int param (int add(int a, int b, int c) { return a + b + c; }), the benchmarks look like this:
Rate Standard Direct
Standard 20533088/s -- -18%
Direct 25184993/s 23% --
Ideally, I'll close the gap a little more between the two but that's a problem for future me.
How It Works
-
Standard trampoline
perl stack -> argument buffer (
void **) -> trampoline -> functionThe trampoline is generic. It expects arguments to be laid out in a specific C array format.
-
Direct trampoline
trampoline -> perl stack -> function
infix generates machine code that calls directly into the perl API (e.g.
SvIV) to fetch values from the Perl stack, puts them into CPU registers, and jumps straight to the target function. We cut out the middleman and reduces pointer indirection layers and memory reads. These are micro-optimizations that produce real world results.
Kitchen Reminders
-
Experimental
This feature is currently marked as experimental because it is not as feature complete as the mainline infix trampolines and Affix's standard functions. It supports primitives (Int, Float, Pointer) robustly, but complex aggregates (Structs by value) may fallback to standard marshalling or behave unexpectedly in edge cases.
-
Restrictions
Direct marshalling relies on knowing exact types at compile time. ...the JIT's compile time not application compile time. It's less forgiving of type mismatches (like passing a Struct where an Int is expected) than the standard path, which often attempts coercion.