智能客服
你问我答,随时在线为你解决问题

























小程序使用JSVM执行JS代码的过程可以分为 Native,JSVM-API,JSVM 三层:
使用JSVM的过程中,可能会因为多种原因产生不必要的开销,导致启动速度变慢。可以从以下三个层面进行分析。
对于使用JSVM的应用启动场景,我们可以区分冷热启动用于分别进行不同的优化。
首先是冷启动,是没有任何profile或者cache可以用于优化的场景,通常是首次启动;
热启动则是已经充分预热,在多次启动之后获取了足量用于优化的cache的场景。
引擎层的开销很大程度上来源于编译。通过合理调整调用JSVM-API时传入的选项,可以降低主线程上JS引擎的编译开销。
以下面的编译接口为例,其中eagerCompile这个参数的开关可以调控编译行为,通过在不同的启动场景打开这个选项可以实现优化效果。
/**
* ...
* @param eagerCompile: Whether to compile the script eagerly.
* ...
*/
JSVM_EXTERN JSVM_Status OH_JSVM_CompileScript(JSVM_Env env,
JSVM_Value script,
const uint8_t* cachedData,
size_t cacheDataLength,
bool eagerCompile, // 开启全量编译
bool* cacheRejected,
JSVM_Script* result); 同时,code cache的生成和使用也会对编译产生影响,这部分可以参考 使用code cache加速编译。
热启动:生成足够多的code cache
热启动场景下,我们会在热启动前生成code cache以减少编译带来的开销。这个时候生成的code cache的覆盖率会影响code cache对热启动的优化效果。
有一个简单的策略可以生成足够的code cache:在生成code cache之前的那次编译中,打开eager compile选项。这样,V8会在编译时进行全量编译,确保生成的code cache是全量的。
这个方法会增加额外的编译时间开销,可能影响冷启动时间。后续将详细讨论native层的冷启动优化方法。
冷启动:使用lazy compile代替eager compile
在冷启动时,eager compile会增加不必要的编译时间。这其中主要的原因是没有拿到v8 lazy compile优化效果:v8会将不在必经路径上的函数推迟编译,在实际运行到的时候才进行编译,这样会减少一些不被运行到函数的编译,从而优化冷启动的时间。
因此,在冷启动时,可以通过关闭eager compile选项来避免阻塞主线程,从而获得足够的冷启动优化效果。
冷启动:减少code cache的影响
上面在考虑减少v8层开销的时候,提到了为了热启动的性能可以开启eager compile进行编译,而为了冷启动性能却又需要关闭eager compile选项,看起来是矛盾的。为了解决这个矛盾,避免在冷热启动性能上的权衡,关键点是在code cache生成本身。
首先,生成code cache需要进行前置编译,其次,生成code cache本身也会产生开销。
在native层,要解决冷启动与生成code cache之间的矛盾,可以另起一个线程用于生成code cache,这样可以避免生成code cache操作对冷启动的影响。
有两个方法可以参考(以下伪代码仅用于展示逻辑流程,不涉及实际的API调用):
async_create_code_cache() {
compile_with_eager_compile();
create_code_cache();
save_code_cache();
}
if (has_code_cache) {
evaluate_script_with_code_cache();
} else {
start_thread(async_create_code_cache());
evaluate_script_without_code_cache();
} async_create_code_cache() {
compile_with_out_eager_compile();
create_code_cache();
save_code_cache();
}
if (has_code_cache) {
evaluate_script_with_code_cache();
} else {
evaluate_script_without_code_cache();
}
if (script_run_completed) {
start_thread(async_create_code_cache());
} 在能达到相同效果时,使用更高效的JSVM-API是一种有效的性能优化方法,以下是一些具体的实践示例。
使用IsXXX代替TypeOf
过去发现,针对仅需要判断对象类型的场景,存在一种相对低效的使用方法:
从OH_JSVM_TypeOf接口获取类型后,再判断是否与某个类型相同。
这种方法需要先查询object的类型,这种方法相对于直接使用is方法会更慢,因此我们新增了针对基础类型的IsXXX系列方法,用更高效的接口代替了相对低效的接口。下面的示例中使用到的JSVM-API可以参考 JSVM数据类型与接口说明,这里仅展示调用的步骤。
bool Test::IsFunction(JSVM_Env env, JSVM_Value jsvmValue) const {
// type judgment
JSVM_ValueType valueType;
OH_JSVM_TypeOf(*env, jsvmValue, &valueType);
return valueType == JSVM_FUNCTION;
} bool Test::IsFunction(JSVM_Env env, JSVM_Value jsvmValue) const {
// type judgment
bool result = false;
OH_JSVM_IsFunction(*env, jsvmValue, &result); // 可直接判断是否为Function类型
return result;
} 以某生态应用小程序场景为例,这个优化可以带来的性能收益端到端有150ms,总占比约5%。
直接使用OH_JSVM_CreateReference,避免创建冗余的object
过去存在这样一种创建reference的路径:
创建一个新的object->设置object的值->创建object的reference。
在已有值的情况下,直接创建值的引用即可。
下面的示例中使用的JSVM-API可以参考 JSVM数据类型与接口说明,这里仅展示调用的步骤。
// (1) open handle scope JSVM_HandleScope scope; OH_JSVM_OpenHandleScope(*env, &scope); // (2) get JSVM_Value JSVM_Value jsvmValue; OH_JSVM_GetNull(*env, &jsvmValue); // (3) create and store Reference for JSVM_Value JSVM_Value wrappingObject; OH_JSVM_CreateObject(*env, &wrappingObject); OH_JSVM_SetElement(*env, wrappingObject, 1, jsvmValue); OH_JSVM_CreateReference(*env, wrappingObject, 1, &result->p_member->jsvmRef); // (4) close handle scope OH_JSVM_CloseHandleScope(*env, scope);
// (1) open handle scope JSVM_HandleScope scope; OH_JSVM_OpenHandleScope(*env, &scope); // (2) get JSVM_Value JSVM_Value jsvmValue; OH_JSVM_GetNull(*env, &jsvmValue); // (3) create and store Reference for JSVM_Value OH_JSVM_CreateReference(*env, jsvmValue, 1, &result->p_member->jsvmRef); // 可从任意对象类型直接创建Reference,代码更为简洁高效 // (4) close handle scope OH_JSVM_CloseHandleScope(*env, scope);
同样以某生态应用小程序场景为例,这个改动减少了大量冗余的接口调用,最终带来的端到端时间收益有100+ms,约3%。
智能客服
你问我答,随时在线为你解决问题
合作咨询
我们的专家服务团队将竭诚为您提供专业的合作咨询服务
解决方案
精准高效的一站式服务支持,助力开发者商业成功