infix
A JIT-Powered FFI Library for C
Loading...
Searching...
No Matches
901_call_overhead.c File Reference

A micro-benchmark to measure the performance overhead of an FFI call. More...

#include "common/double_tap.h"
#include <infix/infix.h>
#include <time.h>
Include dependency graph for 901_call_overhead.c:

Macros

#define DBLTAP_IMPLEMENTATION
 

Functions

int add_for_benchmark (int a, int b)
 The simple C function used as the target for all benchmarked calls.
 
infix_direct_value_t benchmark_int_marshaller (void *source)
 A marshaller for the direct benchmark that reads an int from a pointer.
 
 diag ("infix Call Overhead Benchmark")
 
 diag ("Iterations: %d", BENCHMARK_ITERATIONS)
 
 diag ("Target function: int(int, int)")
 
 for (int i=0;i< BENCHMARK_ITERATIONS;++i) accumulator+
 
 diag ("Direct Call Time: %.4f s (%.2f ns/call)", direct_time, direct_ns_per_call)
 
 if (infix_forward_create_unbound_manual &,,, 2, 2arg_types !=INFIX_SUCCESS)
 
 diag ("infix (Unbound): %.4f s (%.2f ns/call) -> Overhead: ~%.2f ns", unbound_time, unbound_ns, unbound_ns - direct_ns_per_call)
 
 if (infix_forward_create_manual(&bound_t, ret_type, arg_types, 2, 2,(void *) add_for_benchmark) !=INFIX_SUCCESS) bail_out("Failed to create bound trampoline")
 
 diag ("infix (Bound): %.4f s (%.2f ns/call) -> Overhead: ~%.2f ns", bound_time, bound_ns, bound_ns - direct_ns_per_call)
 
 if (infix_forward_create_direct(&direct_t, "(int, int) -> int",(void *) add_for_benchmark, handlers, nullptr) !=INFIX_SUCCESS) bail_out("Failed to create direct trampoline")
 
 diag ("infix (Direct): %.4f s (%.2f ns/call) -> Overhead: ~%.2f ns", direct_marsh_time, direct_marsh_ns, direct_marsh_ns - direct_ns_per_call)
 
 note ("dyncall benchmarking was not enabled.")
 
 pass ("Benchmark completed (final accumulator value: %d)", accumulator)
 
 infix_forward_destroy (unbound_t)
 
 infix_forward_destroy (bound_t)
 
 infix_forward_destroy (direct_t)
 

Variables

 TEST
 
const int BENCHMARK_ITERATIONS = 10000000
 
volatile int accumulator = 0
 
clock_t start = clock()
 
clock_t end = clock()
 
double direct_time = ((double)(end - start)) / CLOCKS_PER_SEC
 
double direct_ns_per_call = (direct_time / BENCHMARK_ITERATIONS) * 1e9
 
infix_typeret_type = infix_type_create_primitive(INFIX_PRIMITIVE_SINT32)
 
infix_typearg_types []
 
infix_forward_tunbound_t = nullptr
 
double unbound_time = ((double)(end - start)) / CLOCKS_PER_SEC
 
double unbound_ns = (unbound_time / BENCHMARK_ITERATIONS) * 1e9
 
infix_forward_tbound_t = nullptr
 
infix_cif_func bound_cif = infix_forward_get_code(bound_t)
 
double bound_time = ((double)(end - start)) / CLOCKS_PER_SEC
 
double bound_ns = (bound_time / BENCHMARK_ITERATIONS) * 1e9
 
infix_forward_tdirect_t = nullptr
 
infix_direct_arg_handler_t handlers [2]
 
infix_direct_cif_func direct_cif = infix_forward_get_direct_code(direct_t)
 
double direct_marsh_time = ((double)(end - start)) / CLOCKS_PER_SEC
 
double direct_marsh_ns = (direct_marsh_time / BENCHMARK_ITERATIONS) * 1e9
 

Detailed Description

A micro-benchmark to measure the performance overhead of an FFI call.

This is not a correctness test, but a performance benchmark. Its purpose is to quantify the "cost" of making a C function call through an infix trampoline compared to a direct C call.

The benchmark measures and reports the average time per call for:

  1. Direct Call: A tight loop of direct C-to-C function calls. This serves as the baseline performance.
  2. **infix (Unbound):** A loop calling the same C function via an unbound forward trampoline. This measures the overhead of the most flexible FFI path.
  3. **infix (Bound):** A loop calling the same C function via a bound forward trampoline. This measures the overhead of the highest-performance FFI path.
  4. dyncall (Optional): If compiled with DYNCALL_BENCHMARK, it also measures the performance of the popular dyncall library for the same function call, providing a useful point of comparison against another FFI library.

The output is a "nanoseconds per call" metric, which helps quantify the FFI overhead and track performance regressions or improvements over time.

Macro Definition Documentation

◆ DBLTAP_IMPLEMENTATION

#define DBLTAP_IMPLEMENTATION

Function Documentation

◆ add_for_benchmark()

int add_for_benchmark ( int  a,
int  b 
)

The simple C function used as the target for all benchmarked calls.

◆ benchmark_int_marshaller()

infix_direct_value_t benchmark_int_marshaller ( void *  source)

A marshaller for the direct benchmark that reads an int from a pointer.

◆ diag() [1/7]

diag ( "Direct Call Time: %.4f s (%.2f ns/call)"  ,
direct_time  ,
direct_ns_per_call   
)

◆ diag() [2/7]

diag ( "infix (Bound): %.4f s (%.2f ns/call) -> Overhead: ~%.2f ns"  ,
bound_time  ,
bound_ns  ,
bound_ns direct_ns_per_call 
)

◆ diag() [3/7]

diag ( "infix (Direct): %.4f s (%.2f ns/call) -> Overhead: ~%.2f ns"  ,
direct_marsh_time  ,
direct_marsh_ns  ,
direct_marsh_ns direct_ns_per_call 
)

◆ diag() [4/7]

diag ( "infix (Unbound): %.4f s (%.2f ns/call) -> Overhead: ~%.2f ns"  ,
unbound_time  ,
unbound_ns  ,
unbound_ns direct_ns_per_call 
)

◆ diag() [5/7]

diag ( "infix Call Overhead Benchmark"  )

◆ diag() [6/7]

diag ( "Iterations: %d"  ,
BENCHMARK_ITERATIONS   
)

◆ diag() [7/7]

diag ( "Target function: int(int, int)"  )

◆ for()

for ( )

◆ if() [1/3]

if ( infix_forward_create_direct(&, ", int -> int"  ,
(void *)  add_for_benchmark,
handlers  ,
nullptr   
)

◆ if() [2/3]

if ( infix_forward_create_manual(&,,, 2, 2, *void  add_for_benchmark)

◆ if() [3/3]

◆ infix_forward_destroy() [1/3]

infix_forward_destroy ( bound_t  )

◆ infix_forward_destroy() [2/3]

infix_forward_destroy ( direct_t  )

◆ infix_forward_destroy() [3/3]

infix_forward_destroy ( unbound_t  )

◆ note()

note ( "dyncall benchmarking was not enabled."  )

◆ pass()

pass ( "Benchmark completed (final accumulator value: %d)"  ,
accumulator   
)

Variable Documentation

◆ accumulator

volatile int accumulator = 0

◆ arg_types

infix_type* arg_types[]
Initial value:
c23_nodiscard infix_type * infix_type_create_primitive(infix_primitive_type_id)
Creates a static descriptor for a primitive C type.
Definition types.c:130
@ INFIX_PRIMITIVE_SINT32
Definition infix.h:171

◆ BENCHMARK_ITERATIONS

const int BENCHMARK_ITERATIONS = 10000000

◆ bound_cif

◆ bound_ns

double bound_ns = (bound_time / BENCHMARK_ITERATIONS) * 1e9

◆ bound_t

◆ bound_time

double bound_time = ((double)(end - start)) / CLOCKS_PER_SEC

◆ direct_cif

◆ direct_marsh_ns

double direct_marsh_ns = (direct_marsh_time / BENCHMARK_ITERATIONS) * 1e9

◆ direct_marsh_time

double direct_marsh_time = ((double)(end - start)) / CLOCKS_PER_SEC

◆ direct_ns_per_call

double direct_ns_per_call = (direct_time / BENCHMARK_ITERATIONS) * 1e9

◆ direct_t

infix_forward_t* direct_t = nullptr

◆ direct_time

double direct_time = ((double)(end - start)) / CLOCKS_PER_SEC

◆ end

end = clock()

◆ handlers

Initial value:
= {{.scalar_marshaller = benchmark_int_marshaller},
{.scalar_marshaller = benchmark_int_marshaller}}
infix_direct_value_t benchmark_int_marshaller(void *source)
A marshaller for the direct benchmark that reads an int from a pointer.
Definition 901_call_overhead.c:40

◆ ret_type

◆ start

start = clock()

◆ TEST

TEST
Initial value:
{
plan(1)
#define plan(count)
Definition double_tap.h:154

◆ unbound_ns

double unbound_ns = (unbound_time / BENCHMARK_ITERATIONS) * 1e9

◆ unbound_t

infix_forward_t* unbound_t = nullptr

◆ unbound_time

double unbound_time = ((double)(end - start)) / CLOCKS_PER_SEC