Skip to content

perf(android): cache the thread's JNIEnv instead of calling GetEnv per JEnv - #123

Open
ammarahm-ed wants to merge 1 commit into
perf/jni-02-stringsfrom
perf/jni-04-jnienv-cache
Open

ammarahm-ed wants to merge 1 commit into
perf/jni-02-stringsfrom
perf/jni-04-jnienv-cache

Conversation

@ammarahm-ed

@ammarahm-ed ammarahm-ed commented Nov 9, 2026 •

Copy link
Copy Markdown
Collaborator

A Java call builds several JEnv objects on its way through the bridge, and each one called JavaVM::GetEnv. JEnv now reads the thread's JNIEnv from a thread_local and only asks the VM when none is set. Both the Node-API and the direct runtime get the change; it is the design from NativeScript/android#2092.

A JNIEnv is only valid while its thread stays attached, so the cache is set only where that lifetime is known:

  • Runtime thread. Runtime::Init pins the env until DestroyRuntime. A worker thread also unpins it right before DetachCurrentThread.
  • JNI entry points. initNativeScript, runModule, runScript, callJSMethodNative and createJSInstanceNative hold the env they were given in an EntryScope for the length of the call and then restore the previous value. A pinned env is left alone.
  • Lazily attached threads. The env GetEnvSlow attaches is never cached, because nothing would tell us when it goes stale.

Results

V8 release build on a Sony SO-01M (Android 11, arm64), AOT-compiled with cmd package compile -m speed. One warm-up launch is discarded, then 5 launches; values are the median ns per call.

Case Node-API before after Δ Direct before after Δ
Void Method on instance 717 694 -3% 680 654 -4%
Void Static Method 362 355 -2% 377 330 -13%
multiply(a, b) 776 749 -3% 878 824 -6%
Instance add(a, b) 1,135 1,097 -3% 1,193 1,105 -7%
Return an Int 367 354 -3% 376 351 -7%
Pass an Int 626 607 -3% 693 636 -8%
Field on instance 910 819 -10% 882 821 -7%
Invoke int callback 10,851 10,264 -5% 11,006 10,623 -3%

The gain is a few percent per call, close to this device's run-to-run noise on single rows (about ±5% on primitive calls), so read the pattern across rows rather than any one cell.

All cases
Case Node-API before after Δ Direct before after Δ
Void Method on instance 717 694 -3% 680 654 -4%
Field on instance 910 819 -10% 882 821 -7%
Int Field on instance 425 381 -10% 387 369 -5%
Get Static Field 898 866 -4% 928 816 -12%
Void Static Method 362 355 -2% 377 330 -13%
multiply(a, b) 776 749 -3% 878 824 -6%
Pass and return string 1,979 1,850 -7% 1,975 2,059 +4%
Return a String 936 873 -7% 922 890 -3%
Return an Int 367 354 -3% 376 351 -7%
Pass a String 1,094 1,073 -2% 1,186 1,168 -2%
Return a Boolean 363 342 -6% 377 333 -12%
Return a Double 464 462 -0% 458 440 -4%
Pass an Int 626 607 -3% 693 636 -8%
Pass a Double 624 593 -5% 702 621 -12%
Pass a Boolean 701 724 +3% 670 633 -6%
Return an Int Array 26,453 27,509 +4% 17,506 17,994 +3%
Pass an Int Array 3,302 3,273 -1% 4,162 4,036 -3%
Return a String Array 27,122 27,111 -0% 19,109 19,576 +2%
Pass a String Array 5,691 5,420 -5% 7,092 6,872 -3%
Return a Double Array 26,921 27,135 +1% 20,357 20,574 +1%
Pass a Double Array 3,144 3,094 -2% 3,987 3,896 -2%
Return a Boolean Array 24,781 25,343 +2% 18,246 18,770 +3%
Pass a Boolean Array 3,014 2,926 -3% 3,842 3,757 -2%
Int Array[0] read 379 366 -3% 248 239 -4%
String Array[0] read 849 783 -8% 698 685 -2%
Double Array[0] read 492 446 -9% 348 343 -1%
Boolean Array[0] read 369 341 -7% 247 236 -4%
Int Array[0] write 546 458 -16% 389 375 -4%
String Array[0] write 1,289 1,128 -13% 1,141 1,060 -7%
Double Array[0] write 551 467 -15% 381 383 +1%
Boolean Array[0] write 552 458 -17% 394 388 -2%
Return a Date Object 29,666 31,058 +5% 23,960 24,197 +1%
Pass a Date Object 3,774 3,871 +3% 3,727 3,512 -6%
Invoke void callback 7,637 7,670 +0% 6,848 6,940 +1%
Instance add(a, b) 1,135 1,097 -3% 1,193 1,105 -7%
Pass and return non-ASCII string 2,007 1,870 -7% 1,959 2,083 +6%
Pass and return 1KB string 5,816 5,698 -2% 5,991 6,066 +1%
Return a Char 409 396 -3% 414 383 -8%
Return this 1,607 1,646 +2% 1,622 1,549 -5%
Return a new object 29,868 30,520 +2% 23,988 24,424 +2%
Call a method on a new object 38,563 38,321 -1% 33,581 33,041 -2%
Pass a 16 Int Array 4,512 4,350 -4% 5,325 5,313 -0%
Pass a 16 Double Array 5,351 4,882 -9% 6,043 5,733 -5%
Invoke int callback 10,851 10,264 -5% 11,006 10,623 -3%
Invoke string callback 8,868 8,652 -2% 8,511 8,281 -3%
Native heap after run (MB) 256 MB 270 MB +5% 178 MB 204 MB +15%

Only V8 was built and run.

@coderabbitai

coderabbitai Bot commented Nov 9, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 38f39822-d68f-440c-9246-25f66fd955c3

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@ammarahm-ed
ammarahm-ed added this pull request to stack #121 November 9, 2026 06:26

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant