Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions benchmark/ffi/get-function-cache-miss.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
'use strict';

// Measures the first getFunction() call on each library instance. This includes
// symbol resolution, signature preparation, and callable creation.
// The `fast` variant is eligible for a generated trampoline on supported
// platforms; `slow` exceeds the x86_64 register budget and falls back.
// Library construction and closing are outside the measured region.

const common = require('../common.js');
const { DynamicLibrary } = require('node:ffi');
const { libraryPath, ensureFixtureLibrary } = require('./common.js');

const bench = common.createBenchmark(main, {
signature: ['fast', 'slow'],
n: [1e3],
});

ensureFixtureLibrary();

const signatures = {
fast: { name: 'add_i32', return: 'i32', arguments: ['i32', 'i32'] },
slow: {
name: 'sum_8_i32',
return: 'i32',
arguments: ['i32', 'i32', 'i32', 'i32', 'i32', 'i32', 'i32', 'i32'],
},
};

function main({ n, signature }) {
const { name, ...definition } = signatures[signature];

// Warm up one-time initialization without populating the measured caches.
const warmup = new DynamicLibrary(libraryPath);
warmup.getFunction(name, definition);
warmup.close();

// Each instance has its own cache, so its first lookup creates a callable.
const libraries = Array.from({ length: n }, () => new DynamicLibrary(libraryPath));
const functions = new Array(n);

bench.start();
for (let i = 0; i < n; ++i)
functions[i] = libraries[i].getFunction(name, definition);
bench.end(n);

for (const lib of libraries)
lib.close();
}
15 changes: 5 additions & 10 deletions benchmark/ffi/get-function.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
'use strict';

// Measures symbol resolution rather than call throughput. Creating a callable
// for a fast-eligible signature emits a native trampoline, so this benchmark
// covers the trampoline allocation path that the call benchmarks never reach.
//
// The `fast` variant is eligible for a generated trampoline; `slow` exceeds the
// x86_64 register budget and falls back, so it resolves without allocating one.
// Comparing the two isolates trampoline creation cost from the rest of symbol
// resolution.
// Measures repeated getFunction() calls with a cached callable rather than
// call throughput. The warmup populates the cache for the measured symbol.
// See get-function-cache-miss.js for first-time resolution, including trampoline
// creation for fast-eligible signatures on supported platforms.

const common = require('../common.js');
const { DynamicLibrary } = require('node:ffi');
Expand All @@ -33,8 +29,7 @@ function main({ n, signature }) {
const { name, ...definition } = signatures[signature];
const lib = new DynamicLibrary(libraryPath);

// Warm up one-time initialization (libffi setup, executable memory probe) so
// it is not attributed to the measured resolutions.
// Populate the callable cache before measuring repeated lookups.
lib.getFunction(name, definition);

bench.start();
Expand Down
Loading