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

A microbenchmark 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)
 
 diag ("infix Call Overhead Benchmark")
 
 diag ("Iterations: %d", BENCHMARK_ITERATIONS)
 
 diag ("Target function: int(int, int)")
 
 for (int i=0;i< BENCHMARK_ITERATIONS;++i)
 
 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 microbenchmark to measure the performance overhead of an FFI call.

Copyright (c) 2025 Sanko Robinson

This source code is dual-licensed under the Artistic License 2.0 or the MIT License. You may choose to use this code under the terms of either license.

SPDX-License-Identifier: (Artistic-2.0 OR MIT)

The documentation blocks within this file are licensed under the Creative Commons Attribution 4.0 International License (CC BY 4.0).

SPDX-License-Identifier: CC-BY-4.0

This is a non-functional test designed to measure the raw speed of the generated FFI trampolines. It should not be run as part of the standard test suite, but rather invoked explicitly when performance analysis is needed.

The test operates in two phases:

  1. Baseline (Direct Call): It measures the time taken to execute millions of direct, native C function calls to a simple int add(int, int) function. This establishes a baseline performance measurement.
  2. FFI Call: It then generates a forward trampoline for the same function and measures the time taken to execute the same number of calls through the FFI.

The difference between these two measurements, divided by the number of iterations, gives the average per-call overhead of the trampoline mechanism in nanoseconds. This is a critical metric for performance-sensitive applications.

An optional comparison against the dyncall library can be enabled by defining the DYNCALL_BENCHMARK macro at compile time.

Macro Definition Documentation

◆ DBLTAP_IMPLEMENTATION

#define DBLTAP_IMPLEMENTATION

Function Documentation

◆ add_for_benchmark()

int add_for_benchmark ( int  a,
int  b 
)

◆ 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 an infix_type descriptor for a primitive C type.
Definition types.c:97
@ INFIX_PRIMITIVE_SINT32
signed int, int32_t
Definition infix.h:140

◆ 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:132

◆ 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