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.
 
 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)
 
 infix_forward_destroy (unbound_t)
 
 infix_forward_destroy (bound_t)
 
 note ("dyncall benchmarking was not enabled.")
 
 pass ("Benchmark completed (final accumulator value: %d)", accumulator)
 

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
 

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.

◆ diag() [1/6]

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

◆ diag() [2/6]

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

◆ diag() [3/6]

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

◆ diag() [4/6]

diag ( "infix Call Overhead Benchmark"  )

◆ diag() [5/6]

diag ( "Iterations: %d"  ,
BENCHMARK_ITERATIONS   
)

◆ diag() [6/6]

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

◆ for()

for ( )

◆ if() [1/2]

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

◆ if() [2/2]

◆ infix_forward_destroy() [1/2]

infix_forward_destroy ( bound_t  )

◆ infix_forward_destroy() [2/2]

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:138
@ INFIX_PRIMITIVE_SINT32
Definition infix.h:186

◆ 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_ns_per_call

double direct_ns_per_call = (direct_time / BENCHMARK_ITERATIONS) * 1e9

◆ direct_time

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

◆ end

end = clock()

◆ ret_type

◆ start

start = clock()

◆ TEST

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

◆ 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