From d93f264ffcf6632c0299b7cd19ab248158452629 Mon Sep 17 00:00:00 2001 From: HoonDongKang Date: Fri, 2 Oct 2026 13:00:04 +0900 Subject: [PATCH] ffi: add getFunction cache-miss benchmark The existing get-function benchmark reuses the callable cached during warmup, so it no longer measures trampoline creation. Add a cache-miss benchmark that resolves a function once per library instance. Construct the instances before timing to exclude library loading while including symbol resolution, signature preparation, and callable creation. Update the existing benchmark comments to describe cache-hit lookups. Signed-off-by: HoonDongKang Assisted-by: Codex:Astra-medium --- benchmark/ffi/get-function-cache-miss.js | 48 ++++++++++++++++++++++++ benchmark/ffi/get-function.js | 15 +++----- 2 files changed, 53 insertions(+), 10 deletions(-) create mode 100644 benchmark/ffi/get-function-cache-miss.js 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();