"NAPI通信耗时长"导致丢帧分析

启动丢帧或者滑动过程丢帧

HarmonyOS
2024-06-13 11:01:53
浏览
收藏 0
回答 1
待解决
回答 1
按赞同
/
按时间
小小肉蟹

1:通过trace看,异步网络请求的完成后处理时间长,导致主线程卡顿。网络请求是从主线程起的异步任务,但是异步任务完成后的回调是在主线程进行的,所以此函数回调耗时会影响主线程卡顿。

2:Napi的线程工作原理如下:complete函数在起线程任务中执行。

3:napi创建异步任务的接口和创建异步任务的业务代码如下,complete函数对应业务代码为:finishFunc函数,但是finishFunc函数实现如下所示,也很简单,只是做了一个赋值。

napi_status napi_create_async_work(napi_env env, 
                                   napi_value async_resource, 
                                   napi_value async_resource_name, 
                                   napi_async_execute_callback execute, 
                                   napi_async_complete_callback complete, 
                                   void* data, 
                                   napi_async_work* result); 
  
napi_value HttpRequestHandler::fetch(napi_env env, std::string url, std::string method, std::string data, HttpHeader header) 
{ 
    AsyncHttpRequestContext* ctx = new AsyncHttpRequestContext(env, url, method, data, header); 
  
    napi_value promise; 
    napi_create_promise(env, &ctx->deferred, &promise); 
  
    napi_value workName = NapiConvertUtil::stdStringToNapiValue(env, "fetchAsync"); 
    napi_create_async_work(env, nullptr, workName, HttpRequestHandler::workFunc, HttpRequestHandler::finishFunc, (void*)ctx, &ctx->work); 
    napi_queue_async_work(env, ctx->work); 
  
    return promise; 
} 
  
void HttpRequestHandler::finishFunc(napi_env env, napi_status status, void * data) 
{ 
    auto ctx = (AsyncHttpRequestContext*)data; 
  
    napi_value result = nullptr; 
    NapiConvertUtil::setPropertyForInt64(env, result, "code", static_cast<int64_t>(ctx->httpCode)); 
    NapiConvertUtil::setPropertyForString(env, result, "msg", ctx->httpMessage); 
    NapiConvertUtil::setPropertyForString(env, result, "data", ctx->recvData); 
  
    napi_resolve_deferred(env, ctx->deferred, result); 
    delete ctx; 
}

4:本地尝试复现,增加日志发现虽然业务代码很简单,只是将native数据 赋值给js。但是耗时较长。

void HttpRequestHandler::finishFunc(napi_env env, napi_status status, void * data) 
{ 
    auto start = std::chrono::high_resolution_clock::now(); 
    auto ctx = (AsyncHttpRequestContext *)data; 
  
    napi_value result = nullptr; 
    NapiConvertUtil::setPropertyForInt64(env, result, "code", static_cast<int64_t>(ctx->httpCode)); 
    NapiConvertUtil::setPropertyForString(env, result, "msg", ctx->httpMessage); 
    NapiConvertUtil::setPropertyForString(env, result, "data", ctx->recvData); 
  
    size_t messageLen = ctx->httpMessage.length(); 
    size_t dataLen = ctx->recvData.length(); 
    napi_resolve_deferred(env, ctx->deferred, result); 
  
    auto end = std::chrono::high_resolution_clock::now(); 
    std::chrono::microseconds cost = std::chrono::duration_cast<std::chrono::microseconds>(end - start); 
    LOG_INFO("finishFunc messgelen:%ld(Byte), datalen:%ld(Byte) cost:%llu(microsecond)", messageLen, dataLen, 
             cost.count()); 
}

3:到此本以为是由于数据copy赋值导致的问题。但是继续观测trace文件中的ArkTsCallBack涌道,发现arkts有业务处理耗时占用比例也极高。因此怀疑该trace打点不止是该函数内。通过与性能工具专家胜军 军哥讨论并通过demo验证,fetch之后的then处理也包含在了这个trace函授打点之内,因此将耗时统计放到napi_resolve_deferred之前,得到的结果确认:该函数本身并不耗时

void HttpRequestHandler::finishFunc(napi_env env, napi_status status, void * data) 
{ 
    auto start = std::chrono::high_resolution_clock::now(); 
    auto ctx = (AsyncHttpRequestContext *)data; 
  
    napi_value result = nullptr; 
    NapiConvertUtil::setPropertyForInt64(env, result, "code", static_cast<int64_t>(ctx->httpCode)); 
    NapiConvertUtil::setPropertyForString(env, result, "msg", ctx->httpMessage); 
    NapiConvertUtil::setPropertyForString(env, result, "data", ctx->recvData); 
  
    size_t messageLen = ctx->httpMessage.length(); 
    size_t dataLen = ctx->recvData.length(); 
    auto end = std::chrono::high_resolution_clock::now(); 
    std::chrono::microseconds cost = std::chrono::duration_cast<std::chrono::microseconds>(end - start); 
    LOG_INFO("finishFunc messgelen:%ld(Byte), datalen:%ld(Byte) cost:%llu(microsecond)", messageLen, dataLen, 
             cost.count()); 
    napi_resolve_deferred(env, ctx->deferred, result); 
}

4:并且通过IDE的ArkTsCallback调用栈看到,确实是异步线程拿到数据之后,对数据进行处理的业务消耗导致:

5:同理:在启动过程中也有 NAPI: complete FileReadText 的业务消耗:

6:该部分内容都是动态布局,需要动态布局进行分析,这些耗时消耗是否可以进行并发处理。

总结:

1:Napi的Compte 回调 在创建异步任务的线程处理,如果是主线程创建,complete回调也在主线程,如果回调耗时,也会影响主线程。

2:由于complete是返回一个promse,因此promise之后then的处理消耗也是在该函数内消耗。即:promise之后的所有回调/Then处理,都在一个线程内。因此需要注意promise/async并没有起线程,promise内部或者then的处理都在同一个线程内。如果有耗时操作,不建议使用promise/async,建议使用TaskPool进行异步调度。

3:trace是通过代码增加的打点,调用栈是通过系统采样采集的,两者侧重的不同,两者可以结合使用,方便问题定位。

分享
微博
QQ
微信
回复
2024-06-13 21:39:25
相关问题
首页LazyForEach predict耗时分析
746浏览 • 1回复 待解决
openharmony napi 异步耗时阻塞js的ui刷新
5383浏览 • 1回复 已解决
JsCreateAVPlayer耗时较长
1541浏览 • 1回复 待解决
性能调试工具及其滑动丢帧案例分享
710浏览 • 1回复 待解决
实现一个自定义动画,出现丢帧问题
282浏览 • 1回复 待解决
Xcomponent绘图流程分析
1152浏览 • 1回复 待解决
HarmonyOS 如何统计应用冷启动耗时
251浏览 • 1回复 待解决