Skip to content

Commit d93f264

Browse files
committed
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 <d159123@naver.com> Assisted-by: Codex:Astra-medium
1 parent fbbf534 commit d93f264

2 files changed

Lines changed: 53 additions & 10 deletions

File tree

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
'use strict';
2+
3+
// Measures the first getFunction() call on each library instance. This includes
4+
// symbol resolution, signature preparation, and callable creation.
5+
// The `fast` variant is eligible for a generated trampoline on supported
6+
// platforms; `slow` exceeds the x86_64 register budget and falls back.
7+
// Library construction and closing are outside the measured region.
8+
9+
const common = require('../common.js');
10+
const { DynamicLibrary } = require('node:ffi');
11+
const { libraryPath, ensureFixtureLibrary } = require('./common.js');
12+
13+
const bench = common.createBenchmark(main, {
14+
signature: ['fast', 'slow'],
15+
n: [1e3],
16+
});
17+
18+
ensureFixtureLibrary();
19+
20+
const signatures = {
21+
fast: { name: 'add_i32', return: 'i32', arguments: ['i32', 'i32'] },
22+
slow: {
23+
name: 'sum_8_i32',
24+
return: 'i32',
25+
arguments: ['i32', 'i32', 'i32', 'i32', 'i32', 'i32', 'i32', 'i32'],
26+
},
27+
};
28+
29+
function main({ n, signature }) {
30+
const { name, ...definition } = signatures[signature];
31+
32+
// Warm up one-time initialization without populating the measured caches.
33+
const warmup = new DynamicLibrary(libraryPath);
34+
warmup.getFunction(name, definition);
35+
warmup.close();
36+
37+
// Each instance has its own cache, so its first lookup creates a callable.
38+
const libraries = Array.from({ length: n }, () => new DynamicLibrary(libraryPath));
39+
const functions = new Array(n);
40+
41+
bench.start();
42+
for (let i = 0; i < n; ++i)
43+
functions[i] = libraries[i].getFunction(name, definition);
44+
bench.end(n);
45+
46+
for (const lib of libraries)
47+
lib.close();
48+
}

‎benchmark/ffi/get-function.js‎

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
11
'use strict';
22

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

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

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

4035
bench.start();

0 commit comments

Comments
 (0)