Skip to content

Commit 68ca809

Browse files
committed
feat: add benchmark (compared to v8 engine)
1 parent be85bab commit 68ca809

File tree

5 files changed

+290
-11
lines changed

5 files changed

+290
-11
lines changed

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,22 @@ try (JSRuntime runtime = quickJS.createJSRuntime()) {
168168
}
169169
```
170170

171+
## Benchmark
172+
173+
| Engine | v8 | QuickJS (Bytecode Mode) |
174+
| :-: | :-: | :-: |
175+
| init | 30ms | 14ms |
176+
| eval | 29ms | 47ms |
177+
| total | 59ms | 61ms |
178+
179+
- Device: Huawei P30 Pro (Kirin 980), Android 10.
180+
- Test JavaScript File: `asset:/sonic.js` (189 KB).
181+
182+
### Conclusion
183+
184+
1. Even when operating in bytecode mode, QuickJS's evaluation time is notably higher than V8's, and this disparity intensifies as the JavaScript file size increases.
185+
2. QuickJS's initialization time is slightly lower than V8's, and this advantage is constant despite of file sizes.
186+
171187
## Concept
172188

173189
QuickJS Android uses the similar APIs to QuickJS.

app/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ android {
4949
dependencies {
5050

5151
implementation project(':quickjs-android')
52+
implementation("com.tencent.hippy:hippy-common:2.12.1")
5253

5354
implementation 'androidx.core:core-ktx:1.9.0'
5455
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
Lines changed: 255 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,257 @@
11
package com.shiqi.testquickjs
2+
import android.content.Context
3+
import android.content.res.Configuration
4+
import android.os.Build
5+
import android.os.Handler
6+
import android.os.Looper
7+
import android.os.Message
8+
import android.text.TextUtils
9+
import android.util.Log
10+
import com.tencent.mtt.hippy.*
11+
import com.tencent.mtt.hippy.bridge.HippyBridge
12+
import com.tencent.mtt.hippy.bridge.HippyBridgeImpl
13+
import com.tencent.mtt.hippy.bridge.HippyBridgeManager
14+
import com.tencent.mtt.hippy.bridge.NativeCallback
15+
import com.tencent.mtt.hippy.bridge.libraryloader.LibraryLoader
16+
import com.tencent.mtt.hippy.common.HippyArray
17+
import com.tencent.mtt.hippy.common.HippyMap
18+
import com.tencent.mtt.hippy.common.ThreadExecutor
19+
import com.tencent.mtt.hippy.devsupport.DevSupportManager
20+
import com.tencent.mtt.hippy.dom.DomManager
21+
import com.tencent.mtt.hippy.modules.HippyModuleManager
22+
import com.tencent.mtt.hippy.uimanager.RenderManager
23+
import com.tencent.mtt.hippy.utils.*
24+
import java.nio.charset.StandardCharsets
25+
import java.util.concurrent.atomic.AtomicBoolean
226

3-
class HippyJsEngine {
4-
}
27+
/**
28+
* Description :
29+
*
30+
* @Author : robertrchen
31+
* @Date : 2022/7/26
32+
*/
33+
class HippyJsEngine(private val context: Context) : HippyBridge.BridgeCallback {
34+
companion object {
35+
36+
private const val TAG = "QuickJs HippyJsEngine"
37+
38+
private const val URI_SCHEME_FILE = "file:"
39+
private const val URI_SCHEME_ASSETS = "asset:"
40+
41+
private val loaded = AtomicBoolean(false)
42+
43+
private fun loadHippy() {
44+
if (loaded.compareAndSet(false, true)) {
45+
LibraryLoader.loadLibraryIfNeed()
46+
}
47+
}
48+
}
49+
50+
private var bridge: HippyBridgeImpl? = null
51+
52+
private fun getCurrentHandler(): Handler {
53+
return Handler(Looper.myLooper() ?: Looper.getMainLooper())
54+
}
55+
56+
fun init() {
57+
loadHippy()
58+
val hippyContext = HippyContext(context)
59+
ContextHolder.initAppContext(hippyContext.getContext())
60+
val v8InitParams = HippyEngine.V8InitParams()
61+
bridge = HippyBridgeImpl(
62+
hippyContext,
63+
this,
64+
false,
65+
false,
66+
false,
67+
"",
68+
v8InitParams
69+
).also { bridge ->
70+
bridge.initJSBridge(
71+
getGlobalConfigs(hippyContext),
72+
object : NativeCallback(getCurrentHandler()) {
73+
override fun Call(
74+
result: Long,
75+
message: Message?,
76+
action: String?,
77+
reason: String?
78+
) {
79+
Log.i(TAG, "hippy init: ${result == 0L}")
80+
81+
runJsFile("asset:/sonic.js", true)
82+
}
83+
}, -1
84+
)
85+
}
86+
}
87+
88+
fun runJsFile(
89+
jsFilePath: String,
90+
isAssetFile: Boolean,
91+
) {
92+
bridge?.let {
93+
val startTime = System.currentTimeMillis()
94+
val nativeCallback = object : NativeCallback(getCurrentHandler()) {
95+
override fun Call(
96+
result: Long,
97+
message: Message?,
98+
action: String?,
99+
reason: String?
100+
) {
101+
Log.i(TAG, "hippy v8 eval, cost: ${System.currentTimeMillis() - startTime}ms ${result == 0L}")
102+
}
103+
}
104+
if (isAssetFile) {
105+
val uri =
106+
if (jsFilePath.startsWith(URI_SCHEME_ASSETS)) jsFilePath else URI_SCHEME_ASSETS + jsFilePath
107+
it.runScriptFromUri(uri, context.assets, false, "", nativeCallback)
108+
109+
} else {
110+
val uri =
111+
if (jsFilePath.startsWith(URI_SCHEME_FILE)) jsFilePath else URI_SCHEME_FILE + jsFilePath
112+
it.runScriptFromUri(uri, null, false, "", nativeCallback)
113+
}
114+
}
115+
}
116+
private fun getGlobalConfigs(hippyContext: HippyContext): String {
117+
val context: Context = hippyContext.getContext()
118+
val globalParams = HippyMap()
119+
val dimensionMap = DimensionsUtil.getDimensions(-1, -1, context, false)
120+
globalParams.pushMap("Dimensions", dimensionMap)
121+
var packageName = ""
122+
var versionName = ""
123+
val extraDataMap = HippyMap()
124+
try {
125+
val packageManager = context.packageManager
126+
val packageInfo = packageManager.getPackageInfo(context.packageName, 0)
127+
if (TextUtils.isEmpty(packageName)) {
128+
packageName = packageInfo.packageName
129+
}
130+
if (TextUtils.isEmpty(versionName)) {
131+
versionName = packageInfo.versionName
132+
}
133+
} catch (var11: Exception) {
134+
var11.printStackTrace()
135+
}
136+
val platformParams = HippyMap()
137+
platformParams.pushString("OS", "android")
138+
platformParams.pushString("PackageName", packageName)
139+
platformParams.pushString("VersionName", versionName)
140+
platformParams.pushInt("APILevel", Build.VERSION.SDK_INT)
141+
platformParams.pushBoolean("NightMode", this.getNightMode())
142+
val Localization = HippyMap()
143+
Localization.pushString("language", I18nUtil.getLanguage())
144+
Localization.pushString("country", I18nUtil.getCountry())
145+
Localization.pushInt("direction", I18nUtil.getLayoutDirection())
146+
platformParams.pushMap("Localization", Localization)
147+
globalParams.pushMap("Platform", platformParams)
148+
val tkd = HippyMap()
149+
tkd.pushString("appName", packageName ?: "")
150+
tkd.pushString("appVersion", versionName ?: "")
151+
tkd.pushMap("extra", extraDataMap)
152+
globalParams.pushMap("tkd", tkd)
153+
return ArgumentUtils.objectToJson(globalParams)
154+
}
155+
156+
private fun getNightMode(): Boolean {
157+
return when ((context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK)) {
158+
Configuration.UI_MODE_NIGHT_UNDEFINED ->
159+
false
160+
Configuration.UI_MODE_NIGHT_NO ->
161+
false
162+
Configuration.UI_MODE_NIGHT_YES ->
163+
true
164+
else -> false
165+
}
166+
}
167+
168+
class HippyContext(private val context: Context) : HippyEngineContext {
169+
170+
private val config: HippyGlobalConfigs
171+
172+
init {
173+
val initParams = HippyEngine.EngineInitParams()
174+
initParams.context = context
175+
config = HippyGlobalConfigs(initParams)
176+
}
177+
178+
fun getContext() = context
179+
180+
override fun getComponentName(): String {
181+
return ""
182+
}
183+
184+
override fun getGlobalConfigs(): HippyGlobalConfigs {
185+
return config
186+
}
187+
188+
override fun getModuleManager(): HippyModuleManager? {
189+
return null
190+
}
191+
192+
override fun getBridgeManager(): HippyBridgeManager? {
193+
return null
194+
}
195+
196+
override fun getDevSupportManager(): DevSupportManager? {
197+
return null
198+
}
199+
200+
override fun getThreadExecutor(): ThreadExecutor? {
201+
return null
202+
}
203+
204+
override fun getDomManager(): DomManager? {
205+
return null
206+
}
207+
208+
override fun getRenderManager(): RenderManager? {
209+
return null
210+
}
211+
212+
override fun getInstance(id: Int): HippyRootView? {
213+
return null
214+
}
215+
216+
override fun addInstanceLifecycleEventListener(listener: HippyInstanceLifecycleEventListener?) {
217+
}
218+
219+
override fun removeInstanceLifecycleEventListener(listener: HippyInstanceLifecycleEventListener?) {
220+
}
221+
222+
override fun addEngineLifecycleEventListener(listener: HippyEngineLifecycleEventListener?) {
223+
}
224+
225+
override fun removeEngineLifecycleEventListener(listener: HippyEngineLifecycleEventListener?) {
226+
}
227+
228+
override fun handleException(throwable: Throwable?) {
229+
}
230+
231+
override fun getStartTimeMonitor(): TimeMonitor? {
232+
return null
233+
}
234+
235+
override fun getEngineId(): Int {
236+
return 0
237+
}
238+
}
239+
240+
override fun callNatives(
241+
moduleName: String?,
242+
moduleFunc: String?,
243+
callId: String?,
244+
params: HippyArray?
245+
) {
246+
TODO("Not yet implemented")
247+
}
248+
249+
override fun reportException(message: String?, stackTrace: String?) {
250+
TODO("Not yet implemented")
251+
}
252+
253+
override fun reportException(e: Throwable?) {
254+
TODO("Not yet implemented")
255+
}
256+
257+
}

app/src/main/java/com/shiqi/testquickjs/MainActivity.kt

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,6 @@ class MainActivity : ComponentActivity() {
3030
// Create a Handler associated with the HandlerThread
3131
val handler = Handler(handlerThread.looper)
3232

33-
val quickJSEngine = QuickJsEngine(applicationContext)
34-
quickJSEngine.init()
35-
val script = quickJSEngine.getScriptFromAsset("asset:/sonic.js")
36-
3733
setContent {
3834
QuickJSTheme {
3935
// A surface container using the 'background' color from the theme
@@ -45,11 +41,27 @@ class MainActivity : ComponentActivity() {
4541
Button(onClick = {
4642
// Post a task to the Handler to run on the HandlerThread
4743
handler.post {
44+
// v8
45+
val hippyJsInitStart = System.currentTimeMillis()
46+
val hippyJsEngine = HippyJsEngine(applicationContext)
47+
hippyJsEngine.init()
48+
val hippyJsInitEnd = System.currentTimeMillis()
49+
Log.i(TAG, "v8 init cost: ${hippyJsInitEnd - hippyJsInitStart}ms")
50+
51+
// quickjs
52+
val quickJsInitStart = System.currentTimeMillis()
53+
val quickJSEngine = QuickJsEngine(applicationContext)
54+
quickJSEngine.init()
55+
val quickJsInitEnd = System.currentTimeMillis()
56+
Log.i(TAG, "quickjs init cost: ${quickJsInitEnd - quickJsInitStart}ms")
57+
58+
val script = quickJSEngine.getScriptFromAsset("asset:/sonic.js")
59+
4860
val bytes = quickJSEngine.getJsContext().compileJsToBytecode(script)
4961
val startTime = System.currentTimeMillis()
50-
Log.i(TAG,"start: $startTime")
5162
quickJSEngine.getJsContext().evaluateBytecode(bytes)
52-
Log.i(TAG, "end, cost: ${System.currentTimeMillis() - startTime}ms")
63+
Log.i(TAG, "quickjs eval, cost: ${System.currentTimeMillis() - startTime}ms")
64+
5365
}
5466
}) {
5567
Text("Click Me")

app/src/main/java/com/shiqi/testquickjs/QuickJsEngine.kt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,6 @@ class QuickJsEngine(private val context: Context) {
5050
}
5151

5252
fun init() {
53-
val start = System.currentTimeMillis()
54-
Log.i(TAG, "init: start at $start")
5553
val quickJS = QuickJS.Builder().build()
5654
try {
5755
jsRuntime = quickJS.createJSRuntime()
@@ -67,7 +65,6 @@ class QuickJsEngine(private val context: Context) {
6765
Log.e(TAG, "init: failed to create js context", e)
6866
return
6967
}
70-
Log.i(TAG, "init: quick js init succeed, cost=${System.currentTimeMillis() - start}")
7168
}
7269

7370
fun getJsContext(): JSContext {

0 commit comments

Comments
 (0)