/
githubmirror
/
node
Обзор
Документация
Войти
/
githubmirror
/
node
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
benchmark/ffi/invoke-function.js
79 строк
3 KB
Yilong Li
ffi: reuse libffi call plans
04 авг 2026, 16:21
Не верифицирован
04 авг 2026, 16:21
31cde9f
Код
Авторство
О чём код?
'use strict'; const assert = require('node:assert'); const common = require('../common.js'); const { libraryPath, ensureFixtureLibrary } = require('./common.js'); // Measure the invocation (call) path for signatures that bypass V8 Fast API // and use libffi through FFIFunction::Invoke(). On x86-64 System V with // libffi >= 3.7, Invoke() reuses a precomputed call plan that avoids repeating // argument-placement work on every call. This benchmark quantifies the // per-call benefit. // // Signatures chosen to bypass both V8 Fast API and keep native work minimal: // - call_int_callback (null): 'function' type forces the generic path; null // pointer triggers the early return in C so native computation is negligible. // From libffi's perspective this is a register-only plan (2 pointer-sized // args both fit in GP registers on x86-64 System V). // - sum_8_i32: 8 GP args exceed the x86-64 Fast API register cap (6), forcing // the generic path. From libffi's perspective 6 args go in registers and 2 // spill to the stack, exercising a stack-spilled plan. const bench = common.createBenchmark(main, { n: [1e7], symbol: ['call_int_callback', 'sum_8_i32'], }, { flags: ['--experimental-ffi', '--no-warnings'], }); ensureFixtureLibrary(); function main({ n, symbol }) { const ffi = require('node:ffi'); if (symbol === 'call_int_callback') { // 'function' type bypasses Fast API (IsFastCallEligible rejects it). // Pass 0n (null function pointer) so the native function returns -1 // immediately without invoking any callback, keeping per-call overhead // dominated by the FFI call machinery itself. const { lib, functions } = ffi.dlopen(libraryPath, { call_int_callback: { return: 'i32', arguments: ['function', 'i32'] }, }); try { // Verify the null-pointer early return. assert.strictEqual(functions.call_int_callback(0n, 7), -1); bench.start(); for (let i = 0; i < n; ++i) functions.call_int_callback(0n, 21); bench.end(n); } finally { lib.close(); } } else { // 8 integer args exceed the x86-64 SysV GP register cap (6), which makes // CreateFastFFIMetadata reject the signature. Calls go through the // SharedBuffer or generic invoker into FFIFunction::Invoke(). const { lib, functions } = ffi.dlopen(libraryPath, { sum_8_i32: { return: 'i32', arguments: [ 'i32', 'i32', 'i32', 'i32', 'i32', 'i32', 'i32', 'i32', ], }, }); const fn = functions.sum_8_i32; assert.strictEqual(fn(1, 2, 3, 4, 5, 6, 7, 8), 36); bench.start(); for (let i = 0; i < n; ++i) fn(1, 2, 3, 4, 5, 6, 7, 14); bench.end(n); lib.close(); } }