目录

JSVM Tuning Practices

JSVM Call Invocation

The process in which a mini-program uses JSVM to execute JS code can be divided into three layers: Native, JSVM-API, and JSVM.

If the application startup is slowed down due to unnecessary overheads generated by using of the JSVM, you can analyze the cause and make optimization in the three layers.

Accelerating Startup

For the applications that use JSVM, optimization can be made in the cold startup and hot startup.

In the cold startup, generally, the initial startup, there is no profile or cache that can be used for optimization.

In the hot startup, the code cache can be used for optimization.

[h2]Reducing Overheads of the JSVM Layer

Most overheads in the JSVM layer are generated from compilation. You can adjust the options passed in when JSVM-API is called to reduce the compilation overhead of the JS engine in the main thread.

In the following example, the eagerCompile parameter specifies the compilation behavior. You can enable this option in the startup scenarios to optimize the compilation effect.

/**
 * ...
 * @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, // Set it to true to enable full compilation.
                                              bool* cacheRejected,
                                              JSVM_Script* result);

Using a code cache also helps speed up compilation. For details, see Accelerating Compilation Using a Code Cache.

Hot Startup: Creating a Code Cache with Adequate Code

To speed up hot startup, create a code cache with adequate code to reduce the compilation overhead. The code coverage of the created code cache determines the optimization effect of the hot startup.

To create a code cache with adequate code, set eagerCompile to true for the compilation before the code cache is created. In this way, full compilation is performed in V8, which ensures high code coverage of the cache.

This approach, however, causes extra compilation time, which may increase the cold startup time used. Read on to learn the ways to optimize the cold startup in the native layer.

Cold Startup: Using Lazy Compile Instead of Eager Compile

Enabling eagerCompile increases the compilation time for cold startup. To reduce the compilation time, you can use V8 lazy compile, which delays the compilation of JS code until it is executed.

By disabling eagerCompile for the code that may block the main thread, you can speed up the cold startup.

[h2]Reducing Time Overheads in the Native Layer

Reducing the Code Cache Impact in Cold Startup

It seems contradictory since you are advised to enable eagerCompile for higher hot startup performance and to disable eagerCompile for higher cold startup performance. To avoid the trade-off between cold startup and hot startup performance, let's focus on the code cache itself.

Code compilation is required before a code cache is created. Moreover, the creation of the code cache generates overheads.

To use a code cache without compromising the cold startup speed in the native layer, you can start another thread to create the code cache.

You can use either of the following methods to achieve this purpose. (No API calling is involved as they are used to demonstrate the logic process.)

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());
}

[h2]Using Performant JSVM-API

You can use more performant APIs provided by JSVM-API to improve performance.

Using IsXXX Instead of TypeOf

To determine the object type, the following method is inefficient:

Use OH_JSVM_TypeOf to obtain the type and check whether the type is the same as a specific type.

Instead, you can use the IsXXX APIs. For details about the JSVM-API used in the following example, see JSVM-API Data Types and APIs. The following example only demonstrates the calling procedure.

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); // Check whether the object type is function.
    return result;
}

The following optimization decreases the code execution time by 150 ms, accounting for about 5% of the performance benefits of a mini-program.

Using OH_JSVM_CreateReference to Avoid Creating Redundant Objects

Generally, the procedure for creating a reference to an object is as follows:

Create an object -> Set a value of the object -> Create the reference to the object

If a value already exists, create a reference to the value.

For details about the JSVM-API used in the following example, see JSVM-API Data Types and APIs. The following example only demonstrates the calling procedure.

// (1) Open a handle scope.
JSVM_HandleScope scope;
OH_JSVM_OpenHandleScope(*env, &scope);
// (2) Obtain JSVM_Value.
JSVM_Value jsvmValue;
OH_JSVM_GetNull(*env, &jsvmValue);
// (3) Create a Reference for JSVM_Value and save it.
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 the handle scope.
OH_JSVM_CloseHandleScope(*env, scope);
// (1) Open a handle scope.
JSVM_HandleScope scope;
OH_JSVM_OpenHandleScope(*env, &scope);
// (2) Obtain JSVM_Value.
JSVM_Value jsvmValue;
OH_JSVM_GetNull(*env, &jsvmValue);
// (3) Create a Reference for JSVM_Value and save it.
OH_JSVM_CreateReference(*env, jsvmValue, 1, &result->p_member->jsvmRef); // Create a reference to an object of any type, making your code simpler and more performant.
// (4) Close the handle scope.
OH_JSVM_CloseHandleScope(*env, scope);

This optimization also helps reduce the number of API calls for another applet and decreases the code execution time by 100+ ms, accounting for about 3% of the performance benefits.