Skip to content

perf(android): pass strings across the bridge as UTF-16 - #122

Open
ammarahm-ed wants to merge 1 commit into
perf/jni-01-benchmarksfrom
perf/jni-02-strings
Open

ammarahm-ed wants to merge 1 commit into
perf/jni-01-benchmarksfrom
perf/jni-02-strings

Conversation

@ammarahm-ed

Copy link
Copy Markdown
Collaborator

Strings cross the JNI bridge as UTF-16 in both directions, in the Node-API and the direct runtime.

JS to Java. ConvertToJavaString read the string as UTF-8 into a new char[] that was never freed, then passed it to NewStringUTF. That leaked a buffer on every string argument (the Node-API heap below), and NewStringUTF takes modified UTF-8, so non-BMP characters came out wrong. It now copies the UTF-16 code units into a stack buffer (heap only past 256 units) and calls NewString.

Java to JS. jstringToJsString now reads GetStringChars instead of re-encoding through GetStringUTFChars. A char return, field read and array element no longer build a java.lang.String first.

Engine layer. The direct runtime gets Value::createStringFromUtf16 and Value::copyUtf16 on V8 (NewFromTwoByte / WriteV2), JSC (JSStringCreateWithCharacters / JSStringGetCharactersPtr), QuickJS-NG (JS_NewStringUTF16 / JS_ToCStringLenUTF16) and Hermes (jsi::String::createFromUtf16 / getStringData), which replaces the hand-written UTF-16 to UTF-8 transcoder in ArgConverter.cpp. Unpaired surrogates survive the round trip.

JSC. JSString::CopyTo in vendor/jsc/jsc-api.cpp copied size bytes instead of size * sizeof(JSChar), so napi_get_value_string_utf16 returned half a string.

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 Δ
Pass and return string 2,340 1,979 -15% 2,186 1,975 -10%
Pass a String 1,168 1,094 -6% 1,295 1,186 -8%
Return a String 976 936 -4% 918 922 +0%
Pass and return non-ASCII string 2,457 2,007 -18% 2,444 1,959 -20%
Pass and return 1KB string 7,395 5,816 -21% 6,667 5,991 -10%
Return a Char 958 409 -57% 481 414 -14%
Native heap after run (MB) 485 MB 256 MB -47% 180 MB 178 MB -1%

Rows this change does not touch moved by up to ±15% between runs (array and object returns by up to ±26%), which is this device's noise floor; only changes well beyond that are attributable.

All cases
Case Node-API before after Δ Direct before after Δ
Void Method on instance 764 717 -6% 688 680 -1%
Field on instance 915 910 -1% 932 882 -5%
Int Field on instance 487 425 -13% 440 387 -12%
Get Static Field 1,053 898 -15% 915 928 +1%
Void Static Method 410 362 -12% 364 377 +4%
multiply(a, b) 774 776 +0% 824 878 +6%
Pass and return string 2,340 1,979 -15% 2,186 1,975 -10%
Return a String 976 936 -4% 918 922 +0%
Return an Int 389 367 -6% 367 376 +2%
Pass a String 1,168 1,094 -6% 1,295 1,186 -8%
Return a Boolean 374 363 -3% 377 377 -0%
Return a Double 518 464 -10% 465 458 -1%
Pass an Int 585 626 +7% 647 693 +7%
Pass a Double 594 624 +5% 655 702 +7%
Pass a Boolean 715 701 -2% 666 670 +1%
Return an Int Array 27,612 26,453 -4% 17,913 17,506 -2%
Pass an Int Array 3,231 3,302 +2% 4,026 4,162 +3%
Return a String Array 26,595 27,122 +2% 19,409 19,109 -2%
Pass a String Array 5,808 5,691 -2% 7,564 7,092 -6%
Return a Double Array 36,162 26,921 -26% 19,724 20,357 +3%
Pass a Double Array 3,699 3,144 -15% 3,886 3,987 +3%
Return a Boolean Array 33,383 24,781 -26% 18,409 18,246 -1%
Pass a Boolean Array 3,439 3,014 -12% 3,907 3,842 -2%
Int Array[0] read 442 379 -14% 254 248 -2%
String Array[0] read 910 849 -7% 675 698 +3%
Double Array[0] read 497 492 -1% 348 348 -0%
Boolean Array[0] read 378 369 -2% 248 247 -1%
Int Array[0] write 542 546 +1% 386 389 +1%
String Array[0] write 1,417 1,289 -9% 1,292 1,141 -12%
Double Array[0] write 524 551 +5% 391 381 -3%
Boolean Array[0] write 521 552 +6% 400 394 -2%
Return a Date Object 28,412 29,666 +4% 22,592 23,960 +6%
Pass a Date Object 4,410 3,774 -14% 3,658 3,727 +2%
Invoke void callback 9,364 7,637 -18% 6,997 6,848 -2%
Instance add(a, b) 1,186 1,135 -4% 1,135 1,193 +5%
Pass and return non-ASCII string 2,457 2,007 -18% 2,444 1,959 -20%
Pass and return 1KB string 7,395 5,816 -21% 6,667 5,991 -10%
Return a Char 958 409 -57% 481 414 -14%
Return this 1,616 1,607 -1% 1,630 1,622 -0%
Return a new object 33,642 29,868 -11% 23,862 23,988 +1%
Call a method on a new object 42,810 38,563 -10% 34,684 33,581 -3%
Pass a 16 Int Array 4,744 4,512 -5% 5,371 5,325 -1%
Pass a 16 Double Array 5,316 5,351 +1% 5,785 6,043 +4%
Invoke int callback 13,217 10,851 -18% 10,889 11,006 +1%
Invoke string callback 10,233 8,868 -13% 8,518 8,511 -0%
Native heap after run (MB) 485 MB 256 MB -47% 180 MB 178 MB -1%

Only V8 was built and run for this table. The JSC, QuickJS-NG and Hermes backends in this change have not been built or run yet.

@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: 9bc34271-81f9-4e69-928c-f5e0e5c3690c

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:19

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