HarmonyOS开发实战( Beta5版)应用TSJS高性能编程工具最佳实践

news2024/9/21 2:36:39

概述

本文参考业界标准,并结合应用TS&JS部分的性能优化实践经验,从应用编程指南、高性能编程实践、性能优化调试工具等维度,为应用开发者提供参考指导,助力开发者开发出高性能的应用。

本文主要提供TS&JS高性能编程实践及如何使用性能优化工具的相关建议。

应用TS&JS高性能编程实践

高性能编程实践,是在开发过程中逐步总结出来的一些高性能的写法和建议,在业务功能实现过程中,我们要同步思考并理解高性能写法的原理,运用到代码逻辑实现中。

本文中的实践示例代码,会统一标注正例或者反例,正例为推荐写法,反例为不推荐写法。

属性访问与属性增删

热点循环中常量提取,减少属性访问次数

在实际的应用场景中抽离出来如下用例,其在循环中会大量进行一些常量的访问操作,该常量在循环中不会改变,可以提取到循环外部,减少属性访问的次数。

【反例】

// 优化前代码
private getDay(year: number): number {
  /* Year has (12 * 29 =) 348 days at least */
  let totalDays: number = 348;
  for (let index: number = 0x8000; index > 0x8; index >>= 1) {
    // 此处会多次对Time的INFO及START进行查找,并且每次查找出来的值是相同的
    totalDays += ((Time.INFO[year- Time.START] & index) !== 0) ? 1 : 0;
  }
  return totalDays + this.getDays(year);
}

可以将Time.INFO[year - Time.START]进行热点函数常量提取操作,这样可以大幅减少属性的访问次数,性能收益明显。

【正例】

// 优化后代码
private getDay(year: number): number {
  /* Year has (12 * 29 =) 348 days at least */
  let totalDays: number = 348;
  const info = Time.INFO[year - Time.START]; // 1. 从循环中提取不变量
  for (let index: number = 0x8000; index > 0x8; index >>= 1) {
    if ((info & index) !== 0) {
      totalDays++;
    }
  }
  return totalDays + this.getDays(year);
}
避免频繁使用delete

delete对象的某一个属性会改变其布局,影响运行时优化效果,导致执行性能下降。

说明:

不建议直接使用delete删除对象的任何属性,如果有需要,建议使用map和set或者引擎实现的高性能容器类。

【反例】

class O1 {
  x: string | undefined = "";
  y: string | undefined = "";
}
let obj: O1 = {x: "", y: ""};

obj.x = "xxx";
obj.y = "yyy";
delete obj.x;

建议使用如下两种写法之一实现属性的增删。

【正例】

// 例1:将Object中不再使用的属性设置为null
class O1 {
  x: string | null = "";
  y: string | null = "";
}
let obj: O1 = {x: "", y: ""};

obj.x = "xxx";
obj.y = "yyy";
obj.x = null;

// 例2:使用高性能容器类操作属性
import HashMap from '@ohos.util.HashMap'; 
let myMap= new HashMap();

myMap.set("x", "xxx");
myMap.set("y", "yyy");
myMap.remove("x");

数值计算

数值计算避免溢出

常见的可能导致溢出的数值计算包括如下场景,溢出之后,会导致引擎走入慢速的溢出逻辑分支处理,影响后续的性能。

  • 针对加法、减法、乘法、指数运算等运算操作,应避免数值大于INT32_MAX或小于INT32_MIN,否则会导致int溢出。

  • 针对&(and)、>>>(无符号右移)等运算操作,应避免数值大于INT32_MAX,否则会导致int溢出。

数据结构

使用合适的数据结构

在实际的应用场景中抽离出来如下用例,该接口中使用JS Object来作为容器去处理Map的逻辑,建议使用HashMap来进行处理。

【反例】

getInfo(t1, t2) {
  if (!this.check(t1, t2)) {
    return "";
  }
  // 此处使用JS Object作为容器
  let info= {};  
  this.setInfo(info);
  let t1= info[t2];
  return (t1!= null) ? t1: "";
}
setInfo(info) {
  // 接口内部实际上进行的是map的操作
  info[T1] = '七六';   
  info[T2] = '九一';
  ... ...
  info[T3] = '十二';
}

代码可以进行如下修改,除了使用引擎中提供的标准内置map之外,还可以使用ArkTS提供的高性能容器类。

【正例】

import HashMap from '@ohos.util.HashMap'; 

getInfo(t1, t2) {
  if (!this.check(t1, t2)) {
    return "";
  }
  // 此处替换为HashMap作为容器
  let info= new HashMap();
  this.setInfo(info);
  let t1= info.get(t2);
  return (t1!= null) ? t1: "";
}
setInfo(info) {
  // 接口内部实际上进行的是map的操作
  info.set(T1, '七六');   
  info.set(T2, '九一');
  ... ...
  info.set(T3, '十二');
}
数值数组推荐使用TypedArray

如果是涉及纯数值计算的场合,推荐使用TypedArray数据结构。

常见的TypedArray包括:Int8Array、Uint8Array、Uint8ClampedArray、Int16Array、Uint16Array、Int32Array、Uint32Array、Float32Array、Float64Array、BigInt64Array、BigUint64Array。

【正例】

const typedArray1 = new Int8Array([1, 2, 3]);  // 针对这一场景,建议不要使用new Array([1, 2, 3])
const typedArray2 = new Int8Array([4, 5, 6]);  // 针对这一场景,建议不要使用new Array([4, 5, 6])
let res = new Int8Array(3);
for (let i = 0; i < 3; i++) {
  res[i] = typedArray1[i] + typedArray2[i];
}
避免使用稀疏数组

分配数组时,应避免其大小超过1024或形成稀疏数组。

虚拟机在分配超过1024大小的数组或者针对稀疏数组,均采用hash表来存储元素,相对使用偏移来访问数组元素速度较慢。

在开发时,尽量避免数组变成稀疏数组。

【反例】

// 如下几种情形会变成稀疏数组
// 1. 直接分配100000大小的数组,虚拟机会处理成用hash表来存储元素
let count = 100000;
let result: number[] = new Array(count);

// 2. 分配数组之后直接,在9999处初始化,会变成稀疏数组
let result: number[] = new Array();
result[9999] = 0;

// 3. 删除数组的element属性,虚拟机也会处理成用hash表来存储元素
let result = [0, 1, 2, 3, 4];
delete result[0];

对象初始化

对象构造初始化

对象构造的时候,要提供默认值初始化,不要访问未初始化的属性。

【反例】

// 不要访问未初始化的属性
class A {
  x: number;
}

// 构造函数中要对属性进行初始化
class A {
  x: number;
  constructor() {
  }
}

let a = new A();
// x使用时还未赋值,这种情况会访问整个原型链
print(a.x);

【正例】

// 推荐一:声明初始化
class A {
  x: number = 0;
}

// 推荐二:构造函数直接赋初值
class A {
  constructor() {
    this.x = 0;
  }
}

let a = new A();
print(a.x);
number正确初始化

针对number类型,编译器在优化时会区分整型和浮点类型。开发者在初始化时如果预期是整型就初始化成0,如果预期是浮点型就初始化为0.0,不要把一个number类型初始化成undefined或者null。

【正例】

function foo(d: number) : number {
  // 变量i预期是整型,不要声明成undefined/null或0.0,直接初始化为0
  let i: number = 0; 
  i += d;
  return i;
}
避免动态添加属性

对象在创建的时候,如果开发者明确后续还需要添加属性,可以提前置为undefined。动态添加属性会导致对象布局变化,影响编译器和运行时优化效果。

【反例】

// 后续obj需要再添加z属性
class O1 {
  x: string = "";
  y: string = "";
}
let obj: O1 = {"x": xxx, "y": "yyy"};
...
// 这种动态添加方式是不推荐的
obj.z = "zzz";

【正例】

class O1 {
  x: string = "";
  y: string = "";
  z: string = "";
}
let obj: O1 = {"x": "xxx", "y": "yyy", "z": ""};
...
obj.z = "zzz";
调用构造函数的入参要与标注类型匹配

由于TS语言类型系统是一种标注类型,不是编译期强制约束,如果入参的实际类型与标注类型不匹配,会影响引擎内部的优化效果。

【反例】

class A {
    private a: number | undefined;
    private b: number | undefined;
    private c: number | undefined;
    constructor(a?: number, b?: number, c?: number) {
        this.a = a;
        this.b = b;
        this.c = c;
    }
}
// new的过程中没有传入参数,a,b,c会获取一个undefined的初值,和标注类型不符
let a = new A();

针对上文的示例场景,开发者大概率预期该入参类型是number类型,需要显式写出来。

参照正例进行如下修改,不然会造成标注的入参是number,实际传入的是undefined。

【正例】

class A {
    private a: number | undefined;
    private b: number | undefined;
    private c: number | undefined;
    constructor(a?: number, b?: number, c?: number) {
        this.a = a;
        this.b = b;
        this.c = c;
    }
}
// 初始化直接传入默认值0
let a = new A(0, 0, 0);
不变的变量声明为const

不变的变量推荐使用const进行初始化。

【反例】

// 该变量在后续过程中并未发生更改,建议声明为常量
let N = 10000;

function getN() {
  return N;
}

【正例】

const N = 10000; 

function getN() {
  return N;
}

接口及继承

避免使用type类型标注

如果传入的参数类型是type类型,实际入参可能是一个object literal,也可能是一个class,编译器及虚拟机因为类型不固定,无法做编译期假设进而进行相应的优化。

【反例】

// type类型无法在编译期确认, 可能是一个object literal,也可能是另一个class Person
type Person = {
  name: string;
  age: number;
};
 
function greet(person: Person) {
  return "Hello " + person.name;
}

// type方式是不推荐的,因为其有如下两种使用方式,type类型无法在编译期确认
// 调用方式一
class O1 {
  name: string = "";
  age: number = 0;
}
let objectliteral: O1 = {name : "zhangsan", age: 20 };
greet(objectliteral);

// 调用方式二
class Person {
  name: string = "zhangsan";
  age: number = 20;
}
let person = new Person();
greet(person);

【正例】

interface Person {
  name: string ;
  age: number;
}
 
function greet(person: Person) {
  return "Hello " + person.name;
}

class Person {
  name: string = "zhangsan";
  age: number = 20;
}

let person = new Person();
greet(person);

函数调用

声明参数要和实际的参数一致

声明的参数要和实际的传入参数个数及类型一致,如果不传入参数,则会作为undefined处理,可能造成与实际入参类型不匹配的情况,从而导致运行时走入慢速路径,影响性能。

【反例】

function add(a: number, b: number) {
  return a + b;
}
// 参数个数是2,不能给3个
add(1, 2, 3);
// 参数个数是2,不能给1个
add(1);
// 参数类型是number,不能给string
add("hello", "world");

【正例】

function add(a: number, b: number) {
  return a + b;
}
// 按照函数参数个数及类型要求传入参数
add(1, 2);
函数内部变量尽量使用参数传递

能传递参数的尽量传递参数,不要使用闭包。闭包作为参数会多一次闭包创建和访问。

【反例】

let arr = [0, 1, 2];

function foo() {
  // arr 尽量通过参数传递
  return arr[0] + arr[1];
}
foo();

【正例】

let arr = [0, 1, 2];

function foo(array: Array) : number {
  // arr 尽量通过参数传递
  return array[0] + array[1];
}
foo(arr);

函数与类声明

避免动态声明function与class

不建议动态声明function和class。

以如下用例为例,动态声明了class Add和class Sub,每次调用foo都会重新创建class Add和class Sub,对内存和性能都会有影响。

【反例】

function foo(f: boolean) {
  if (f) {
    return class Add{};
  } else {
    return class Sub{};
  }
}

【正例】

class Add{};
class Sub{};
function foo(f: boolean) {
  if (f) {
    return Add;
  } else {
    return Sub;
  }
}

TS&JS性能优化工具使用

通过如下工具和使用方法,能够帮助开发者查看待分析场景下各阶段的耗时分布情况,并进一步针对耗时情况使用对应的工具做细化分析。

工具使用介绍:

  1. 针对应用开发者,推荐使用自带的Smartperf工具来进行辅助分析,可以从宏观角度查看应用各个阶段耗时分布情况,快速找到待分析优化模块。
  2. 针对第一步分析得到的待优化模块,需要进行进一步分析确认耗时点是在TS&JS部分还是C++部分。C++部分耗时模块细化分析建议使用hiperf工具;针对TS&JS部分耗时,可以使用CPU Profiler工具。
  3. 针对虚拟机开发者,如果需要进一步拆分细化,推荐使用虚拟机提供的RUNTIME_STAT工具。

Smartperf工具使用指导

以如下某个应用场景使用过程的trace为例,可以通过Smartperf工具抓取到应用使用阶段的耗时信息,其中大部分为GC(Garbage Collection,垃圾回收)等操作。如果此接口大部分是应用开发者通过TS&JS实现,并且在trace中体现此阶段比较耗时,则可以继续使用CPU Profiler工具来进一步分析TS&JS部分耗时情况。

除了可以查看系统的trace之外,还可以在应用的源码的关键流程中加入一些trace点,用于做性能分析。startTrace用于记录trace起点,finishTrace用于记录trace终点,在应用中增加trace点的方式如下:

import hiTraceMeter from '@ohos.hiTraceMeter';
... ...
hiTraceMeter.startTrace("fillText1", 100);
... ...
hiTraceMeter.finishTrace("fillText1", 100);

在应用层或Native层增加trace点,具体可见 性能打点跟踪开发指导。

hiperf工具使用指导

集成在Smartperf的hiperf工具使用指导,具体可见 HiPerf的抓取和展示说明。

hiperf工具的单独使用指导,具体可见 hiperf应用性能优化工具。

TS&JS及NAPI层面耗时分析工具

TS&JS层面耗时主要分为如下几种情况:

  1. Ability的生命周期回调的耗时。

  2. 组件的TS&JS业务代码的回调的耗时。

  3. 应用TS&JS逻辑代码耗时。

NAPI层面的耗时主要分为如下几种情况:

  1. TS&JS业务代码通过调用JS API产生的耗时。

  2. TS&JS业务代码调用开发者通过NAPI封装的C/C++实现时产生的耗时。

针对应用中的TS&JS及NAPI两种业务场景的耗时分析,CPU Profiler工具,用来识别热点函数及耗时代码。

其支持的采集方式如下:

  • DevEco Studio连接设备实时采集;

  • hdc shell连接设备进行命令行采集。

可以通过CPU Profiler工具,对TS&JS中执行的热点函数进行抓取。以应用实际使用场景为例,在此场景中,可以抓到应用中的某一热点函数,在此基础上,针对该接口做进一步分析。

最后

小编在之前的鸿蒙系统扫盲中,有很多朋友给我留言,不同的角度的问了一些问题,我明显感觉到一点,那就是许多人参与鸿蒙开发,但是又不知道从哪里下手,因为资料太多,太杂,教授的人也多,无从选择。有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)文档用来跟着学习是非常有必要的。 

为了确保高效学习,建议规划清晰的学习路线,涵盖以下关键阶段:

希望这一份鸿蒙学习文档能够给大家带来帮助~


 鸿蒙(HarmonyOS NEXT)最新学习路线

该路线图包含基础技能、就业必备技能、多媒体技术、六大电商APP、进阶高级技能、实战就业级设备开发,不仅补充了华为官网未涉及的解决方案

路线图适合人群:

IT开发人员:想要拓展职业边界
零基础小白:鸿蒙爱好者,希望从0到1学习,增加一项技能。
技术提升/进阶跳槽:发展瓶颈期,提升职场竞争力,快速掌握鸿蒙技术

2.视频教程+学习PDF文档

(鸿蒙语法ArkTS、TypeScript、ArkUI教程……)

 纯血版鸿蒙全套学习文档(面试、文档、全套视频等)

                   

鸿蒙APP开发必备

​​

总结

参与鸿蒙开发,你要先认清适合你的方向,如果是想从事鸿蒙应用开发方向的话,可以参考本文的学习路径,简单来说就是:为了确保高效学习,建议规划清晰的学习路线

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2097823.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

什么是短视频矩阵?一个人能做好短视频矩阵营销吗?

很多人认为做短视频矩阵就是多账号、多发视频就可以了&#xff0c;但其实做短视频矩阵&#xff0c;并不仅仅是更多账号更多视频那么简单&#xff0c;它的核心在于搭建一个全方位的内容传播方式。这种方式包括三个方面&#xff1a;账号矩阵、平台矩阵和内容矩阵。 首先是账号矩阵…

TikTok运营:IP地址如何影响TikTok的内容运营?

TikTok作为外贸人宣传推广的重要平台&#xff0c;其运营成效与产品的实际转化率息息相关。然而&#xff0c;在TikTok的运营过程中&#xff0c;一个看似微不足道的元素—IP地址&#xff0c;却扮演着至关重要的角色。本文将深入探讨TikTok运营中IP地址的重要性&#xff0c;揭示其…

炫700头猪!所有长久的关系,都是讲条件的——早读(逆天打工人爬取热门微信文章解读)

A股呀A股你好狠呀 引言Python 代码第一篇 洞见 所有长久的关系&#xff0c;都是讲条件的第二篇 华为小黑子&#xff1f;结尾 &#xff08;这哥们是来炫他家的700头猪的吧&#xff0c;等我有钱了&#xff0c;包圆咯&#xff09; 引言 周末工作一天 休息一天 周六上班 那天晚上…

8Manage PM:掌握高效项目进度跟踪的关键策略

在负责管理众多项目的同时&#xff0c;管理人员最不希望遭遇因项目进度滞后而产生额外负担。 项目可能会因资源分配不当、范围蔓延以及其他意外中断等因素而遭受延误&#xff0c;有时这种延误甚至可能是长期的。 因此&#xff0c;掌握项目进度跟踪的方法相当重要。在整个项目…

Unity(2022.3.41LTS) - UI详细介绍-InputField(输入字段)

目录 零.简介 一、基本功能与用途 二、组件介绍 三、使用方法 四、优化和注意事项 零.简介 在 Unity 中&#xff0c;输入字段&#xff08;Input Field&#xff09;是一个非常实用的用户界面组件&#xff0c;以下是更详细的介绍&#xff0c;包括组件方面的深入分析&#x…

深入理解MySQL慢查询优化(2) -- SQL的执行流程

要优化一条SQL语句&#xff0c;要先理解SQL操作的执行流程 1. 不同SQL操作的执行流程 1.1 order by order by用于排序&#xff0c;如果用于排序的列上没有索引&#xff0c;就需要把整张表加载进内存进行排序&#xff0c;非常耗时。如果有索引&#xff0c;因为B树存储的数据本…

计算机毕业设计选题推荐-高校一卡通系统-Java/Python项目实战

✨作者主页&#xff1a;IT毕设梦工厂✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Py…

史记——我与历史的缘妙

究天人之际&#xff0c;通古今之变&#xff0c;成一家之言。 注解&#xff1a;这句话出自司马迁《史记》之《报任安书》。意思是通过“史实”现象揭示本质,探究自然现象和人类社会之间的相依相对关系。通晓从古到今的社会的各种发展演变,进而寻找历代王朝兴衰成败之道理。通过…

【Unity编辑器扩展】SpriteAltas资源一键转换为TMP_SpriteAsset或Sprite图集

【Unity编辑器扩展】艺术字/自定义图片字体/TextMeshPro艺术字生成工具_unity 艺术字-CSDN博客 博文工具源码见GF_X自动化游戏开发框架&#xff1a;GitHub - sunsvip/GF_X: Unity GameFramework HybridCLR&#xff0c;Includes several automated editor extension tools, an …

vue 批量导出pdf 压缩包 zip

vue 批量导出pdf 压缩包 zip 使用插件 html2canvas jspdf jszip &#xff08;百度ai搜出来的是zip-js 这个没法安装&#xff09; file-saver 思路&#xff1a; 1.使用 html2canvasjspdf 将页面转图片转pdf&#xff08;这个怎么转的可以网上搜下很多&#xff09; 2.利用jszipfil…

10个精选ArcGIS图源分享第4辑

数据是GIS的血液。 我们在《10个精选ArcGIS图源分享第3辑》一文中为你分享了10个ArcGIS图源&#xff0c;现在又增加了10个新的图源作为第4辑分享给大家。 并提供了能直接在ArcMap和ArcGIS Pro打开的文件&#xff0c;如果你需要这些ArcGIS图源&#xff0c;请在文末查看该数据的…

iPhone出现4013错误的和解决方案分享

在苹果设备用户群体中&#xff0c;遇到iTunes错误4013是一个颇为棘手的问题。这个错误通常发生在尝试更新iOS系统、恢复iPhone或iPad时&#xff0c;导致操作无法顺利完成。本文将为你提供一系列实用的解决方案&#xff0c;帮助你摆脱iPhone 4013错误的困扰。 一、了解错误4013的…

AI大模型编写多线程并发框架(六十四):监听器优化·下

系列文章目录 文章目录 系列文章目录前言一、项目背景二、第十一轮对话-修正运行时数据三、修正任务计数器四、第十二轮对话-生成单元测试五、验证通过七、参考文章 前言 在这个充满技术创新的时代&#xff0c;AI大模型正成为开发者们的新宠。它们可以帮助我们完成从简单的问答…

面向智能体编程(Agent-Oriented Programming, AOP)

大家已经熟知面向对象编程、面向接口编程&#xff0c;AI大模型研发背景下&#xff0c;又产生了一个新概念&#xff1a;&#xff08;Agent-Oriented Programming, AOP&#xff09; 它是一种特殊的编程范式&#xff0c;它专注于开发能够模拟人类智能行为的智能体。智能体是能够在…

水经微图Web版功能简介

我们在《水经微图Web版341项功能清单》一文中&#xff0c;为你罗列了水经微图&#xff08;下称“微图”&#xff09;的详细功能清单。 现在基于该清单&#xff0c;再为你分享微图最主要的功能&#xff0c;从而让你对该平台有一个基本的了解。 微图Web版功能简介 微图Web版&a…

Vue3 官方推荐状态管理库Pinia

介绍 Pinia 是 Vue 官方团队推荐代替Vuex的一款轻量级状态管理库&#xff0c;允许跨组件/页面共享状态。 Pinia 旨在提供一种更简洁、更直观的方式来处理应用程序的状态。 Pinia 充分利用了 Vue 3 的 Composition API。 官网&#xff1a; Pinia符合直觉的 Vue.js 状态管理库 P…

我如何选择自己的AI细分方向和第一个入门项目

思维导图 下图展示了我的思考和分析过程 目录 思维导图大纲1. **确定兴趣和技能方向**2. **寻找合适的开源项目**3. **评估项目的活跃度**4. **开始贡献**5. **展示你的贡献**6. **推荐开源项目**总结 选择一个细分领域1. **了解各个领域的基本概念**2. **考虑你的兴趣和背景**…

备战秋招60天算法挑战,Day29

题目链接&#xff1a; https://leetcode.cn/problems/decode-ways/ 视频题解&#xff1a; https://www.bilibili.com/video/BV181YKeGE3E/ LeetCode 91. 解码方法 题目描述 一条包含字母 A-Z 的消息通过以下映射进行了 编码 &#xff1a; A -> "1" B -> &qu…

大力出奇迹背景下的Scaling Law能否带领我们走向AGI

Scaling Law&#xff08;尺度定律&#xff09; 在人工智能领域&#xff0c;尤其是在大模型的发展中扮演着至关重要的角色。它描述了模型性能如何随着模型规模&#xff08;如参数数量&#xff09;、数据量和计算资源的增加而提升。这一定律对于理解大模型的能力扩展和优化训练策…