diff --git a/benchmark/ffi/get-function-cache-miss.js b/benchmark/ffi/get-function-cache-miss.js new file mode 100644 index 000000000000..9cb559c6ecb7 --- /dev/null +++ b/benchmark/ffi/get-function-cache-miss.js @@ -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(); +} diff --git a/benchmark/ffi/get-function.js b/benchmark/ffi/get-function.js index 939c1630ab64..7b885f1c6bdf 100644 --- a/benchmark/ffi/get-function.js +++ b/benchmark/ffi/get-function.js @@ -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'); @@ -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();