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

Kitchen Reminders