Skip to content

Commit 0e40153

Browse files
committed
perf: reduce the overhead of binding function call (#139)
* perf: reduce the overhead of binding function call * cache callback info object in scope * reduce closure variables captured in binding function that results 1.4x fast * MIN_CHROME_VERSION 85
1 parent e2de33f commit 0e40153

File tree

10 files changed

+64
-127
lines changed

10 files changed

+64
-127
lines changed

packages/bench/CMakeLists.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ add_compile_definitions(
1919
"NODE_ADDON_API_ENABLE_MAYBE"
2020
)
2121
add_link_options(
22-
"-sMIN_CHROME_VERSION=84"
22+
"-sMIN_CHROME_VERSION=85"
2323
"-sALLOW_MEMORY_GROWTH=1"
2424
"-sMODULARIZE=1"
2525
)
@@ -38,11 +38,13 @@ target_link_libraries(emnapic PRIVATE emnapi-basic fib)
3838
target_link_options(emnapic PRIVATE
3939
"-sEXPORTED_FUNCTIONS=['_napi_register_wasm_v1','_malloc','_free']"
4040
"-sEXPORT_NAME=emnapic"
41+
"-sEXPORTED_RUNTIME_METHODS=['emnapiInit']"
4142
)
4243

4344
add_executable(emnapicpp "${CMAKE_CURRENT_SOURCE_DIR}/src/lib.cpp")
4445
target_link_libraries(emnapicpp PRIVATE emnapi-basic fib)
4546
target_link_options(emnapicpp PRIVATE
4647
"-sEXPORTED_FUNCTIONS=['_napi_register_wasm_v1','_malloc','_free']"
4748
"-sEXPORT_NAME=emnapicpp"
49+
"-sEXPORTED_RUNTIME_METHODS=['emnapiInit']"
4850
)

packages/emnapi/common.gypi

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@
8585
'-sNODEJS_CATCH_EXIT=0',
8686
'-sNODEJS_CATCH_REJECTION=0',
8787
'-sWASM_BIGINT=1',
88-
'-sMIN_CHROME_VERSION=84',
88+
'-sMIN_CHROME_VERSION=85',
8989
'-sMIN_NODE_VERSION=161500',
9090
'-sSTACK_SIZE=<(stack_size)',
9191
'-sDEFAULT_PTHREAD_STACK_SIZE=<(stack_size)',
@@ -102,7 +102,7 @@
102102
'-sNODEJS_CATCH_EXIT=0',
103103
'-sNODEJS_CATCH_REJECTION=0',
104104
'-sWASM_BIGINT=1',
105-
'-sMIN_CHROME_VERSION=84',
105+
'-sMIN_CHROME_VERSION=85',
106106
'-sMIN_NODE_VERSION=161500',
107107
'-sSTACK_SIZE=<(stack_size)',
108108
'-sDEFAULT_PTHREAD_STACK_SIZE=<(stack_size)',

packages/emnapi/src/function.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export function napi_get_cb_info (env: napi_env, cbinfo: napi_callback_info, arg
3131
$CHECK_ENV!(env)
3232
const envObject = emnapiCtx.envStore.get(env)!
3333
if (!cbinfo) return envObject.setLastError(napi_status.napi_invalid_arg)
34-
const cbinfoValue = emnapiCtx.cbinfoStack.get(cbinfo)!
34+
const cbinfoValue = emnapiCtx.scopeStore.get(cbinfo)!.callbackInfo
3535

3636
from64('argc')
3737
from64('argv')
@@ -179,9 +179,15 @@ export function napi_get_new_target (
179179

180180
from64('result')
181181

182-
const cbinfoValue = emnapiCtx.cbinfoStack.get(cbinfo)!
183-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
184-
const value = cbinfoValue.getNewTarget(envObject)
182+
const cbinfoValue = emnapiCtx.scopeStore.get(cbinfo)!.callbackInfo
183+
const { thiz, fn } = cbinfoValue
184+
185+
const value = thiz == null || thiz.constructor == null
186+
? 0
187+
: thiz instanceof fn
188+
? envObject.ensureHandleId(thiz.constructor)
189+
: 0
190+
185191
makeSetValue('result', 0, 'value', '*')
186192
return envObject.clearLastError()
187193
}

packages/emnapi/src/internal.ts

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,24 +14,33 @@ export function emnapiCreateFunction<F extends (...args: any[]) => any> (envObje
1414
const functionName = (!utf8name || !length) ? '' : (emnapiString.UTF8ToString(utf8name, length))
1515

1616
let f: F
17+
const napiCallback = makeDynCall('ppp', 'cb')
18+
const callback = (envObject: Env) => {
19+
return napiCallback(envObject.id, envObject.ctx.scopeStore.currentScope.id)
20+
}
1721

18-
const makeFunction = () => function (this: any): any {
22+
const makeFunction = (envObject: Env, callback: (env: Env) => any) => function (this: any): any {
1923
'use strict'
20-
const cbinfo = emnapiCtx.cbinfoStack.push(this, data, arguments, f)
21-
const scope = emnapiCtx.openScope(envObject)
24+
const scope = envObject.ctx.openScope(envObject)
25+
const callbackInfo = scope.callbackInfo
26+
callbackInfo.data = data
27+
callbackInfo.args = arguments
28+
callbackInfo.thiz = this
29+
callbackInfo.fn = f
2230
try {
23-
return envObject.callIntoModule((envObject) => {
24-
const napiValue = makeDynCall('ppp', 'cb')(envObject.id, cbinfo)
25-
return (!napiValue) ? undefined : emnapiCtx.handleStore.get(napiValue)!.value
26-
})
31+
const napiValue = envObject.callIntoModule(callback)
32+
return (!napiValue) ? undefined : envObject.ctx.handleStore.get(napiValue)!.value
2733
} finally {
28-
emnapiCtx.cbinfoStack.pop()
29-
emnapiCtx.closeScope(envObject, scope)
34+
callbackInfo.data = 0
35+
callbackInfo.args = undefined!
36+
callbackInfo.thiz = undefined
37+
callbackInfo.fn = undefined!
38+
envObject.ctx.closeScope(envObject, scope)
3039
}
3140
}
3241

3342
if (functionName === '') {
34-
f = makeFunction() as F
43+
f = makeFunction(envObject, callback) as F
3544
return { status: napi_status.napi_ok, f }
3645
}
3746

@@ -41,7 +50,7 @@ export function emnapiCreateFunction<F extends (...args: any[]) => any> (envObje
4150

4251
// #if DYNAMIC_EXECUTION
4352
if (emnapiCtx.feature.supportNewFunction) {
44-
const _ = makeFunction()
53+
const _ = makeFunction(envObject, callback)
4554
try {
4655
f = (new Function('_',
4756
'return function ' + functionName + '(){' +
@@ -50,15 +59,15 @@ export function emnapiCreateFunction<F extends (...args: any[]) => any> (envObje
5059
'};'
5160
))(_)
5261
} catch (_err) {
53-
f = makeFunction() as F
62+
f = makeFunction(envObject, callback) as F
5463
if (emnapiCtx.feature.canSetFunctionName) Object.defineProperty(f, 'name', { value: functionName })
5564
}
5665
} else {
57-
f = makeFunction() as F
66+
f = makeFunction(envObject, callback) as F
5867
if (emnapiCtx.feature.canSetFunctionName) Object.defineProperty(f, 'name', { value: functionName })
5968
}
6069
// #else
61-
f = makeFunction() as F
70+
f = makeFunction(envObject, callback) as F
6271
if (emnapiCtx.feature.canSetFunctionName) Object.defineProperty(f, 'name', { value: functionName })
6372
// #endif
6473
return { status: napi_status.napi_ok, f }

packages/runtime/src/CallbackInfo.ts

Lines changed: 0 additions & 76 deletions
This file was deleted.

packages/runtime/src/Context.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import {
1919
NAPI_VERSION_EXPERIMENTAL,
2020
NODE_API_DEFAULT_MODULE_API_VERSION
2121
} from './util'
22-
import { CallbackInfoStack } from './CallbackInfo'
2322
import { NotSupportWeakRefError, NotSupportBufferError } from './errors'
2423
import { Reference, ReferenceWithData, ReferenceWithFinalizer, type ReferenceOwnership } from './Reference'
2524
import { type IDeferrdValue, Deferred } from './Deferred'
@@ -117,7 +116,6 @@ export class Context {
117116
public refStore = new Store<Reference>()
118117
public deferredStore = new Store<Deferred>()
119118
public handleStore = new HandleStore()
120-
public cbinfoStack = new CallbackInfoStack()
121119
private readonly refCounter?: NodejsWaitingRequestCounter
122120
private readonly cleanupQueue: CleanupQueue
123121

packages/runtime/src/HandleScope.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,22 @@
11
import type { Handle, HandleStore } from './Handle'
22
import { External } from './External'
33

4+
export interface ICallbackInfo {
5+
thiz: any
6+
data: void_p
7+
args: ArrayLike<any>
8+
fn: Function
9+
}
10+
411
export class HandleScope {
512
public handleStore: HandleStore
613
public id: number
714
public parent: HandleScope | null
815
public child: HandleScope | null
916
public start: number
1017
public end: number
11-
public _escapeCalled: boolean
18+
private _escapeCalled: boolean
19+
public callbackInfo: ICallbackInfo
1220

1321
public constructor (handleStore: HandleStore, id: number, parentScope: HandleScope | null, start: number, end = start) {
1422
this.handleStore = handleStore
@@ -19,6 +27,12 @@ export class HandleScope {
1927
this.start = start
2028
this.end = end
2129
this._escapeCalled = false
30+
this.callbackInfo = {
31+
thiz: undefined,
32+
data: 0,
33+
args: undefined!,
34+
fn: undefined!
35+
}
2236
}
2337

2438
public add<V> (value: V): Handle<V> {
@@ -32,6 +46,7 @@ export class HandleScope {
3246
}
3347

3448
public dispose (): void {
49+
if (this._escapeCalled) this._escapeCalled = false
3550
if (this.start === this.end) return
3651
this.handleStore.erase(this.start, this.end)
3752
}

packages/runtime/src/ScopeStore.ts

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,16 @@ import { HandleScope } from './HandleScope'
55
export class ScopeStore {
66
private readonly _rootScope: HandleScope
77
public currentScope: HandleScope
8+
private readonly _values: [undefined, ...HandleScope[]]
89

910
constructor () {
1011
this._rootScope = new HandleScope(null!, 0, null, 1, HandleStore.MIN_ID)
1112
this.currentScope = this._rootScope
13+
this._values = [undefined]
1214
}
1315

1416
get (id: number): HandleScope | undefined {
15-
id = Number(id)
16-
let scope = this.currentScope
17-
while (scope !== this._rootScope) {
18-
if (scope.id === id) {
19-
return scope
20-
}
21-
scope = scope.parent!
22-
}
23-
return undefined
17+
return this._values[id]
2418
}
2519

2620
openScope (envObject: Env): HandleScope {
@@ -29,9 +23,10 @@ export class ScopeStore {
2923

3024
if (scope !== null) {
3125
scope.start = scope.end = currentScope.end
32-
scope._escapeCalled = false
3326
} else {
34-
scope = new HandleScope(envObject.ctx.handleStore, currentScope.id + 1, currentScope, currentScope.end)
27+
const id = currentScope.id + 1
28+
scope = new HandleScope(envObject.ctx.handleStore, id, currentScope, currentScope.end)
29+
this._values[id] = scope
3530
}
3631
this.currentScope = scope
3732

@@ -48,18 +43,7 @@ export class ScopeStore {
4843
}
4944

5045
dispose (): void {
51-
let scope: HandleScope | null = this.currentScope
52-
while (scope !== null) {
53-
scope.handleStore = null!
54-
scope.id = 0
55-
scope.parent = null
56-
scope.start = HandleStore.MIN_ID
57-
scope.end = HandleStore.MIN_ID
58-
scope._escapeCalled = false
59-
const child: HandleScope | null = scope.child
60-
scope.child = null
61-
scope = child
62-
}
63-
this.currentScope = null!
46+
this.currentScope = this._rootScope
47+
this._values.length = 1
6448
}
6549
}

packages/runtime/src/index.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
export { CallbackInfo, CallbackInfoStack } from './CallbackInfo'
21
export { createContext, getDefaultContext, Context, type CleanupHookCallbackFunction } from './Context'
32
export { Deferred, type IDeferrdValue } from './Deferred'
43
export { Env, NodeEnv, type IReferenceBinding } from './env'
@@ -7,7 +6,7 @@ export { External, isExternal, getExternalValue } from './External'
76
export { Finalizer } from './Finalizer'
87
export { TrackedFinalizer } from './TrackedFinalizer'
98
export { Handle, ConstHandle, HandleStore } from './Handle'
10-
export { HandleScope } from './HandleScope'
9+
export { HandleScope, type ICallbackInfo } from './HandleScope'
1110
export { Persistent } from './Persistent'
1211
export { Reference, ReferenceWithData, ReferenceWithFinalizer, ReferenceOwnership } from './Reference'
1312
export { RefTracker } from './RefTracker'

packages/test/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ if(IS_EMSCRIPTEN)
8686
"-sNODEJS_CATCH_EXIT=0"
8787
"-sWASM_BIGINT=1"
8888
"-sALLOW_MEMORY_GROWTH=1"
89-
"-sMIN_CHROME_VERSION=84"
89+
"-sMIN_CHROME_VERSION=85"
9090
"-sSTACK_SIZE=1048576"
9191
"-sDEFAULT_PTHREAD_STACK_SIZE=1048576"
9292
"-sINITIAL_MEMORY=16777216"

0 commit comments

Comments
 (0)