0%

iOS审核

因为内容透露过多,暂时关闭本类型,如需详细内容请与我联系

iOS Image 内存优化

结论

在iOS 原生层,对于图片的使用,推荐使用 UIGraphicsImageRenderer API

也需要合理在不同的使用场景,根据场景需要类型使用正确的API ,同时对图片资源做加载和释放管理。

前因

看看上面的这个图,有没有考虑过,iOS 里面一张 4096 * 3072 尺寸的png图片占用多大内存呢 ?答案是惊人的 48M

计算公式是这样的 image width * image height * 4 /1024 / 1024 这里认为 image 属于RGBA8888

问题来了,内存都在哪里 ?

深究一下iOS 内存分配逻辑可以有下面的结论,iOS 内存部分分为三类,即:Data Buffer、Image Buffer、Frame Buffer

Data Buffer 是存储在内存中的原始数据,图像可以使用不同的格式保存,如 jpg、png。是Image 的文件内容。

Image Buffer 是图像在内存中的存在方式,用于存放图像具体素点信息。Image Buffer 的大小和图像的大小成正比。

Frame BufferImage Buffer 内容相同,不过其存储在 vRAM(video RAM)中,而 Image Buffer 存储在 RAM 中。

解码就是从 Data Buffer 生成 Image Buffer 的过程。Image Buffer 会占用带宽上传到 GPU 成为 Frame Buffer,最后GPU负责使用 Frame Buffer用于更新显示区域。

大致执行流程如上图,先经过载入,加载图像内容到内存成为Data Buffer , 然后就是经过Decode 过程,转化图像为GPU 可用的 Image Buffer ,在需要显示的时候Image Buffer data 会被上传到GPU 中成为Frame Buffer Data 进行相应渲染。

上图飙升的 48M 内存代码如下

1
2
3
4
5
6
7
8
9
10
//原图加载
-(void)test
{
UIImageView *imageView = [[UIImageView alloc]init];
UIImage *image = [UIImage imageNamed:@"bg_teacherLetter"];
imageView.image = image;
imageView.frame = CGRectMake(0, 0, 1367*0.5, 1089*0.5);
[self.view addSubview:imageView];
}

UIImage 是 iOS 中处理图像的高级类。创建一个 UIImage 实例只会加载 Data Buffer,将图像显示到屏幕上才会触发解码,也就是 Data Buffer 解码为 Image Buffer。Image Buffer 也关联在 UIImage 上。

imageNamed 这个常用API 存在一个内存问题,就是载入以后图片会被缓存到系统Cache 里面。 这是一种便捷的设计,比如可以快速在cache 里面查找到图片的缓存,同时也是一个弊端,内存就放在了cache 里面,一部分内存就会被持续占用。如果是比较小,又常用的图片,这么处理比较合适,但是针对于例子中的尺寸来讲,就非常不合适。

对于这个问题,大家通用的解决方案应该是 使用 imageWithContentsOfFile这个 API 来搞,根据苹果的解释是:使用这个方法创建的图片不会缓存于系统缓存内,开发者可在适当的时机对图片进行处理。因而,对于一些比较大的或不常使用的图片,我们应当使用imageWithContentsOfFile:进行创建。

1
2
3
4
5
6
7
8
9
10
11
-(void)withContexOfFile{
UIImageView *imageView = [[UIImageView alloc]init];
UIImage *image = [ViewController imageWithContentName:@"bg_teacherLetter@2x.png"];
imageView.image = image;
imageView.frame = CGRectMake(0, 0, 1367*0.5, 1089*0.5);
[self.view addSubview:imageView];
}
+ (UIImage *)imageWithContentName:(NSString *)name
{
return [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:name ofType:nil]];
}

API 的本身用途是不在cache 里面缓存图片内容,但是图片占用的内存依然很大。

优点 : 图片不使用即释放内存,不存在图片常驻内存
缺点 : 每次使用都需要做IO的操作

适用于使用不频繁的大图加载

有没有更好的方式来降低图片内存 ?

答案,有! 参考 Image and Graphics Best Practices WWDC2018

所以引出这里的重点: UIGraphicsImageRenderer

代码先:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-(void)resizeTest
{
UIImageView *imageView = [[UIImageView alloc]init];
UIImage *image = [UIImage imageNamed:@"bg_teacherLetter"];
image = [self resiImage:image size:CGSizeMake(1367*0.5, 1089*0.5)];
imageView.image = image;
imageView.frame = CGRectMake(0, 0, 1367*0.5, 1089*0.5);
[self.view addSubview:imageView];
}
- (UIImage*)resiImage:(UIImage *)image size:(CGSize)size{
UIGraphicsImageRenderer *re = [[UIGraphicsImageRenderer alloc]initWithSize:size];
return [re imageWithActions:^(UIGraphicsImageRendererContext * _Nonnull rendererContext) {
[image drawInRect:CGRectMake(0, 0, size.width, size.height)];
}];
}

先给出一组数据对比 图片尺寸同样缩放在 1367 / 2 , 1089 / 2

图1 使用 imageNamed API , 发现 Physical footprint 明显增加了 48M, 内存块儿在 IOSurface

IOSurface 内存块儿增加是因为 图片decode 之后生成的位图,内存会被分类到这里

图2 使用 UIGraphicsImageRenderer API

明显观察到的内容是 占用巨大的 IOSurface 不存在了,但是内存块儿多了一个 CG raster data 大小为 5824K

1367 * 1089 * 4 / 1024 = 5815.08984375 跟 5824 很接近是不是 ? 可是又会有一点儿感觉不对,图片的宽和高都没有按照缩放的size 进行计算。于是就这个问题又去查了一下。

UIGraphicsImageRenderer 实现的原理是,系统可以根据图片分辨率选择创建解码图片的格式,如选用SRGB format 格式,每个像素占用 4 字节,而Alpha 8 format,每像素只占用 1 字节,因此可以减少大量的解码内存占用。

使用 UIGraphicsImageRenderer 之后这张图片physical footprint增加了多少内存 ? 0.2M

那么优化了多少内存呢 (48 - 0.2) / 48 = 0.996 , 释放了 96% 的可用空间。

C++ 跨平台业务相关实例

static_lib

前因

我们业务方有多端:iOS 、Android 、win、Mac 。于是需要使用跨平台统一的相关业务功能。

现状是存在多端的开发人员,多个技术栈,如果加上管理和沟通不畅,一个需求会有多个版本。以最好的状态来估计,出现不同的逻辑但是业务都正常是有可能的。但是只要有人员介入参与项目开发,出现问题是必然的。如何查找和后期稳定维护将是一个巨大的灾难。

那么如果使用C++来作为基础业务开发,好处是显而易见的,即直接编译成为二进制,速度优势明显。但是缺陷是,如果开发人员出现了很小的失误,带来的问题是巨大的,毕竟太多的用户不太愿意升级App

回归正题

正常来讲,C++ 在跨平台游戏引擎中比较常见。也因为业务需要分端开发的成本比较高,C++ 作为底层通用组件对于跨平台应用来讲是一个不错的选择。根据这一点简单讲一下如何才能使用一套代码进行四端编译。

需要先科普一下静态库和动态库。

静态库

静态库即静态链接库(Windows 下的 .lib,Linux 和 Mac 下的 .a)。之所以叫做静态,是因为静态库在编译的时候会被直接拷贝一份,复制到目标程序里,这段代码在目标程序里就不会再改变了。

静态库的好处很明显,编译完成之后,库文件实际上就没有作用了。目标程序没有外部依赖,直接就可以运行。当然其缺点也很明显,就是会使用目标程序的体积增大。

动态库

动态库即动态链接库(Windows 下的 .dll,Linux 下的 .so,Mac 下的 .dylib/.tbd)。与静态库相反,动态库在编译时并不会被拷贝到目标程序中,目标程序中只会存储指向动态库的引用。等到程序运行时,动态库才会被真正加载进来。

动态库的优点是,不需要拷贝到目标程序中,不会影响目标程序的体积,而且同一份库可以被多个程序使用(因为这个原因,动态库也被称作共享库)。

我们知道动态库有各种好处,比如包体比较小,可以被多个库使用等,但是由于iOS 在审核时候对于动态库是不持推荐态度,而且,需要依赖系统环境等。

我们有一个原生需求,就是使用C++ 版本的Websocket, 使用底层提供公共功能。

选型

在GitHub 上面,根据排名列了一下顺序。

websocketpp 4.8K star

boostorg/beast 3.1K star

warmcat/libwebsockets 3.1K star

以稳定和快速开发,采用了websocketpp 。 没有采用 libwebsocket 的另外一个原因是,在比较少的示例代码中,查找异常出现的错误码都是一件极为难的事情。

websocketpp 这个库没什么特别大的问题,唯一的问题是需要依赖boost 库,生成二进制比较大。

问题呢都不是很大,吭哧吭哧的搞好几天,boost 相关依赖环境和相关编译环境就配置好了,下一步就是如何生成相应的静态库。

项目比较特殊,遗留问题,游戏引擎部分选择了不开源的Laya . 又因为业务侧,laya 自己封装的libWebsocket 在遇到相关网络问题的时候力不重新,所以才会有使C++ 版本Websocket 来替换掉 Laya 提供的Websocket 的需求。

结构如需求,很简单,通过基础boost 提供基础相关能力,在上层去扩充子插件。通过插件提供的基础能力,封装相应接口暴露给上层去使用。

因为本身项目的问题,

Appium 自动化测试验证步骤

Memary Guage increase

目的:

​ 为了自动化测试APP,想要高效验证内容

已知:

​ 可用有,PrefDog ,Appium, 且PrefDog 已经有完善的相关自动化脚本

存在问题:

​ 自动化在于教师端,需要操作教师端,客户端用于性能验证,单一的监听

​ 我们要求肯定不能满足于这样,我需要客户端自动操作,用于验证内容。

结论

​ Appium 对于iOS 自动化测试很好用,但是对于我们的Hybrid类型App 似乎不是特别好用。

折腾步骤

  1. 新电脑,安装环境啥的,因为网上有太多的安装教程,这里不想多说。Appium Mac 安装教程

  2. 给设备安装 WebDriverAgent 推荐去Appium 的安装包下面去运行, 具体的方法参考

1
2
cd /Applications/Appium.app/Contents/Resources/app/node_modules/appium/node_modules/appium-WebDriverAgent
# 里面是xcode 工程 打开,先编译库,在编译iOS 工程, 注意签名 最后安装设备 command + u
  1. 打开Appium 客户端,配置如下
1
2
3
4
5
6
7
8
9
10
{
"platformName": "ios",
"platformVersion": "13.6",
"udid": "eac242486bcffe0d9fcc233bce4b5f52ec4df738",
"deviceName": "iPad",
"automationName": "XCUITest",
"bundleId": "com.xxxxxx.xxx",
"xcodeOrgId": "4xxxxZ3",
"xcodeSigningId": "iPhone Developer"
}
Memary Guage increase
  1. 点击 Start Session 前提 Xcode 在运行当前 WebDriverAgent ,然后 Appium 会自动拉起设备上指定的bundleId App

    Memary Guage increase

问题来了,发现App Source 里面没有我们想要的button 之类的,因为没有特殊id 就没办法进行相应自动化。我们的项目是给予Laya Native 的App , 显示区域是JavaScript 调用GLKView进行绘制,所以,抓不到iOS UI 控件应该是正常的。

有没有其他方式

在准备宣布说无法调试Laya JavaScript 的时候,我看到了这个 appium 使用ios_webkit_debug_proxy

感觉我又有了希望,安装步骤依然忽略,安装以后配置appium

1
2
3
4
5
6
7
8
9
10
11
{
"platformName": "ios",
"platformVersion": "13.6",
"udid": "eac242486bcffe0d9fcc233bce4b5f52ec4df738",
"deviceName": "iPad",
"automationName": "XCUITest",
"xcodeOrgId": "4XXXXXX3",
"xcodeSigningId": "iPhone Developer",
"startIWDP": true,
"browserName": "Safari"
}

能够完美拉起 Safari , 也能看到Safari 里面的标签内容,但是不是我们想要的App 内容

Memary Guage increase

相关配置放到Appium 里面是可以直接拉起Safari 的,但是不能拉起我们指定的App

瞅了一下Appium 关于 hybrid App的测试例子,大多数是使用了Webview 技术来profile 相关HTML Context 内容。这个跟我们现有项目不符,验证到此可以结束了。

结论

Appium 确实是一个很优秀的自动化测试框架,可以通过大家熟知的语言进行相关自动化测试,能够非常方便的提高测试的效率,增加测试结果的可靠性。这一切都需要针对正常的iOS或者Android App , 对于 hybrid App 也有非常不错的测试方式,相关测试代码也是非常的简洁。对于正常类型App 测试,强烈推荐。

iOS 内存分析

Memary Guage increase

​ 最近在做关于APP 内存方面的分析,去尽量优化iOS 侧 APP 的内存。原本使用的是之前自己掌握的方式,比如 Memory leaks , Memory Allocations 每一项工具都有针对性的内容,在进一步优化的时候发现了原来可以使用终端方式来查询问题。一个对于自己全新的领域。

​ 引述一下关键字:iOS Memory Deep Dive WWDC2018

​ WWDC2018 , 2018年 。大概如果是苹果开发铁杆粉都已经悉数掌握。但是很多开发者似乎对这块儿知识都了解较少,如何具体使用也不一定都能掌握。我这儿整理一下对自己查找问题中的一些相关性,也顺带做一下总结,提醒一下自己。

前言

​ 利用自己在实际开发中遇到的相关问题来具体讲解如何在iOS中做内存分析,怎样去针对性优化。

  • 怎样去观察内存占用

  • 需要卸载掉哪些内存

怎样观察内存

​ 大家都在Xcode 下经常开发,Xcode Memory gauge 这个界面大家应该都不会陌生

Memary Guage

​ 在当前界面,能够提供数字,百分比,内存使用危险程度的通用观察界面。针对于平常查看内存占用大小比较方便,但是对于内存问题,这块儿能提供的内容相当有限。但是能够提示开发者内存占用的波动,从而引起关注。

iOS 内存介绍

iOS Memory Deep Dive 讲了关于iOS 在内存方面的使用和统计方式。提到了在iOS 操作系统中,内存的统计方式是按照分页的方式。内存又会被细分为 物理内存 和虚拟内存。 APP 的运行是一定要基于内存之上的,物理内存和虚拟内存的主要作用如下。

物理内存:设备运行时为操作系统和各种程序提供临时储存空间

虚拟内存:为每一个进程提供了一个一致的、私有的地址空间;其主要作用是:保护了每个进程的地址空间不会被其他进程破坏,降低内存管理的复杂性。 虚拟内存是进程运行时所有内存空间的总和,并且可能有一部分不在物理内存中。

​ iOS系统是按页分配内存的,每个page通常是16KB

1
Memory in use = Number of pages * Page size

​ iOS内存可以分为clean memory和dirty memory。当用户(也就是程序员)申请分配内存时,系统只会对这块内存进行标记,这时只会分配虚拟内存,而不会分配物理内存,此时内存是clean memory。当对这块内存进行数据填充时,才会分配物理内存,内存变为dirty memory。

dirty_memory
1
Memory Footprint = Dirty Memory + Compressed Memory

针对内存问题,我们考虑的重点也是去减少 Dirty Memory

具体查找步骤

根据已知的信息,尝试去实践一下如何查找内存消耗,这次使用命令行方式。

以第一张图 27.8M 为例。首先修改 iOS Scheme , 勾选 Malloc Stack Logging 并选中为 Live Aloocations Only

ios_scheme

然后就是运行中等待时机到来,点击Debug Memory Graph 得到内存图。可以通过 File --> Export Memory Graph到处文件到指定位置。

1
2
#终端执行下面代码
$ vmmap -summary memory.memgraph # 首先获取内存分配的概况,使用这条命令可以获得
vmmap_summary

从我们已知的信息可以知道 , Dirty Memory 是我们关注的重点。于是发现了 REGION TYPE 是 MALLOC_LARGE

1
$ vmmap -verbose memory.memgraph | grep "MALLOC_LARGE"  #获取详细的关于内存分配的 在归属于 “MALLOC_LARGE” 这里的内容

vmmap_verbose

从图中我们可以容易的发现,内存地址为 0x1146e4000 。 在这样的情况下,我们可以借助于另外一个命令 malloc_history

1
$ malloc_history memory.memgraph -fullStacks 0x1146e4000   # 获取地址块儿的堆栈信息
malloc_history

根据我们查到的调用堆栈,我们发现是因为 new 操作,导致了内存分配,源头应该在于 InstanceDemo 这个类的构造函数。

source_code

贴上源码,是这里引起的内存分配无疑。我们同时也发现,Dirty memory 也是 9.8M. 如果我们不给数组赋值呢?

我们注释掉数组赋值,重新走一遍儿上面的流程。

vmmap_summary_new

于是发现了,同样 MALLOC_LARGE 中的 VIRTUAL SIZE 是 10M , 但是DIRTY SIZE 只剩下了 208K

如果我们细跟入进去会发现

malloc_history_new

是同样能够查到内存分配的位置。但是因为只有内存申请,没有使用,分配的区域被放在了虚拟内存,所以Dirty MEMARY 并没有相应标记。

在查询内存问题的时候还有另外一个命令可以使用,就是

1
$ leaks -traceTree  0x1118cc000 memory_new.memgraph

leaks_tool

通过这个命令可以知道 m_instance 被持有引用,可能会出现内存泄漏。

Demo 依旧会提供,主要对内存如何分析和查找可能性提出一些方法。 我们同时还可以通过Xcode 提供的 VM Tracker 等工具用来具体指向我们怀疑的点。一般来讲,图片资源占用的内存会比较大,如果作为优化的方向,首选见效处理就是规范化图片资源。

Laya Native 性能分析和改造笔记

分析和修改步骤:

1、使用laya编辑器 , 2.1.0 版本的,导出生成 2.1.0 版本的ios 工程为基础

为了验证编译导出的laya 二进制文件能否放入到低版本,先对2.1.0 版本ios 工程做了以下修改

改动部分如下:

  1. 替换laya脚本导出的 libconch.a 文件
  2. 替换高版本(2.11.0)apploader.js , index.js ,挪动版本 webglPlus.js 到低版本工程

改动后代码可以直接正常运行。

profile 内存使用情况 (更换后,游戏运行状态) 170M (现象,也同时证明laya已有js 代码可以直接运行在Laya 高版本运行环境)

Laya%20Native%20%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90%E5%92%8C%E6%94%B9%E9%80%A0%E7%AC%94%E8%AE%B0%2067e77270abfb45999959e8f3991ec77d/Untitled.png

问题:

在直接更改iOS源码,强制卸载laya部分,表现如下,内存并没有如预期下降。

Laya%20Native%20%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90%E5%92%8C%E6%94%B9%E9%80%A0%E7%AC%94%E8%AE%B0%2067e77270abfb45999959e8f3991ec77d/Untitled%201.png

在profile 里面去查找还持有,存在于内存的一些对象,可以发现如下(两个占用内存的大户)

Laya%20Native%20%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90%E5%92%8C%E6%94%B9%E9%80%A0%E7%AC%94%E8%AE%B0%2067e77270abfb45999959e8f3991ec77d/Untitled%202.png

把相关退出代码搬运到 laya 提供的项目工程中,通过debug方式profile ,能够看到每次重新进入laya 内存部分被重新分配了 30+ M (每一次都会增加,认定引擎侧出现内存泄漏,在于C++或者ObjectC)

比如可以很直观发现下面的泄漏。

Laya%20Native%20%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90%E5%92%8C%E6%94%B9%E9%80%A0%E7%AC%94%E8%AE%B0%2067e77270abfb45999959e8f3991ec77d/Untitled%203.png

优先解决内存占用大户,发现特征是如下,不能指向具体的代码片段,但是也提供了相应的信息,libglxxxx 推测是在OpenGL 侧。(可能是C++ 持有,属于重点怀疑,但是这个库又是iOS自己提供,所以ObjectC 也不能排除)

Laya%20Native%20%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90%E5%92%8C%E6%94%B9%E9%80%A0%E7%AC%94%E8%AE%B0%2067e77270abfb45999959e8f3991ec77d/Untitled%204.png

Laya%20Native%20%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90%E5%92%8C%E6%94%B9%E9%80%A0%E7%AC%94%E8%AE%B0%2067e77270abfb45999959e8f3991ec77d/Untitled%205.png

在一次profile 时候,在资源载入的时候发现了JCLayaGl 和texture2d 的字段,所以怀疑为C++ 本身内存泄漏的可能性又在加强。libglxxxx 这个东西依然存在,可以先做排除法,先不去管texure2d这块儿。

Laya%20Native%20%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90%E5%92%8C%E6%94%B9%E9%80%A0%E7%AC%94%E8%AE%B0%2067e77270abfb45999959e8f3991ec77d/Untitled%206.png

不使用texure2d的最直接方式是不给图片资源,于是,更换为无图片资源小游戏demo,测试如下

Laya%20Native%20%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90%E5%92%8C%E6%94%B9%E9%80%A0%E7%AC%94%E8%AE%B0%2067e77270abfb45999959e8f3991ec77d/Untitled%207.png

依然存在大量的 Gl 字段, 而且还能看到很多 allocated pair to attach … 这货又好像是iOS的内存泄漏

Laya%20Native%20%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90%E5%92%8C%E6%94%B9%E9%80%A0%E7%AC%94%E8%AE%B0%2067e77270abfb45999959e8f3991ec77d/Untitled%208.png

Laya%20Native%20%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90%E5%92%8C%E6%94%B9%E9%80%A0%E7%AC%94%E8%AE%B0%2067e77270abfb45999959e8f3991ec77d/Untitled%209.png

所以基本认定,在iOS Object C 侧,卸载部分存在内存泄漏,在C++侧也大致会存在相应的内存泄漏。那么下面面临的问题就是如何修复。

C++通用内存泄漏部分基本是在于 Init 或者 构造函数 中 new 出一些对象,在析构时候没有干掉,导致一些内存常驻。

Object C 里面也有C++ 类似的地方,alloc 出对象,然后强制引用,导致对象不能正常释放。

所以基本解决的思路就有了,下一步,干活验证。

Laya%20Native%20%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90%E5%92%8C%E6%94%B9%E9%80%A0%E7%AC%94%E8%AE%B0%2067e77270abfb45999959e8f3991ec77d/Untitled%2010.png

ios 如下图,现有学生端 profile 内存部分,也能找到在上面魔改的Demo 工程中出现的内存消耗大户,所以laya 本身存在 destory 接口,但是内部逻辑却没有做到位。既然有,那么我们强制析构对象的时机和入口就很明确了。

Laya%20Native%20%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90%E5%92%8C%E6%94%B9%E9%80%A0%E7%AC%94%E8%AE%B0%2067e77270abfb45999959e8f3991ec77d/Untitled%2011.png

测试内存和性能比对:

替换修改好的laya二进制到学生端 卸载前

Laya%20Native%20%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90%E5%92%8C%E6%94%B9%E9%80%A0%E7%AC%94%E8%AE%B0%2067e77270abfb45999959e8f3991ec77d/Untitled%2012.png

卸载后

Laya%20Native%20%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90%E5%92%8C%E6%94%B9%E9%80%A0%E7%AC%94%E8%AE%B0%2067e77270abfb45999959e8f3991ec77d/Untitled%2013.png

Laya%20Native%20%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90%E5%92%8C%E6%94%B9%E9%80%A0%E7%AC%94%E8%AE%B0%2067e77270abfb45999959e8f3991ec77d/Untitled%2014.png

明显看到内存能够降下去!!!

具体内存和性能对比

laya 两个版本运行时内存比较

新版本

Laya%20Native%20%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90%E5%92%8C%E6%94%B9%E9%80%A0%E7%AC%94%E8%AE%B0%2067e77270abfb45999959e8f3991ec77d/Untitled%2015.png

Laya%20Native%20%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90%E5%92%8C%E6%94%B9%E9%80%A0%E7%AC%94%E8%AE%B0%2067e77270abfb45999959e8f3991ec77d/Untitled%2016.png

Laya%20Native%20%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90%E5%92%8C%E6%94%B9%E9%80%A0%E7%AC%94%E8%AE%B0%2067e77270abfb45999959e8f3991ec77d/Untitled%2017.png

Laya%20Native%20%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90%E5%92%8C%E6%94%B9%E9%80%A0%E7%AC%94%E8%AE%B0%2067e77270abfb45999959e8f3991ec77d/Untitled%2018.png

旧版本

旧有的占用内存量

Laya%20Native%20%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90%E5%92%8C%E6%94%B9%E9%80%A0%E7%AC%94%E8%AE%B0%2067e77270abfb45999959e8f3991ec77d/Untitled%2019.png

点击切换,干掉laya

Laya%20Native%20%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90%E5%92%8C%E6%94%B9%E9%80%A0%E7%AC%94%E8%AE%B0%2067e77270abfb45999959e8f3991ec77d/Untitled%2020.png

再次拉起

Laya%20Native%20%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90%E5%92%8C%E6%94%B9%E9%80%A0%E7%AC%94%E8%AE%B0%2067e77270abfb45999959e8f3991ec77d/Untitled%2021.png

会持续增加

按照 登录→大厅→录播课→回到大厅

新版本二进制,移除laya 时候内存

Laya%20Native%20%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90%E5%92%8C%E6%94%B9%E9%80%A0%E7%AC%94%E8%AE%B0%2067e77270abfb45999959e8f3991ec77d/Untitled%2022.png

Laya%20Native%20%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90%E5%92%8C%E6%94%B9%E9%80%A0%E7%AC%94%E8%AE%B0%2067e77270abfb45999959e8f3991ec77d/Untitled%2023.png

Laya%20Native%20%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90%E5%92%8C%E6%94%B9%E9%80%A0%E7%AC%94%E8%AE%B0%2067e77270abfb45999959e8f3991ec77d/Untitled%2024.png

Laya%20Native%20%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90%E5%92%8C%E6%94%B9%E9%80%A0%E7%AC%94%E8%AE%B0%2067e77270abfb45999959e8f3991ec77d/Untitled%2025.png

Laya%20Native%20%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90%E5%92%8C%E6%94%B9%E9%80%A0%E7%AC%94%E8%AE%B0%2067e77270abfb45999959e8f3991ec77d/Untitled%2026.png

Laya%20Native%20%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90%E5%92%8C%E6%94%B9%E9%80%A0%E7%AC%94%E8%AE%B0%2067e77270abfb45999959e8f3991ec77d/Untitled%2027.png

没有做修改的内存

Laya%20Native%20%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90%E5%92%8C%E6%94%B9%E9%80%A0%E7%AC%94%E8%AE%B0%2067e77270abfb45999959e8f3991ec77d/Untitled%2028.png

Laya%20Native%20%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90%E5%92%8C%E6%94%B9%E9%80%A0%E7%AC%94%E8%AE%B0%2067e77270abfb45999959e8f3991ec77d/Untitled%2029.png

Laya%20Native%20%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90%E5%92%8C%E6%94%B9%E9%80%A0%E7%AC%94%E8%AE%B0%2067e77270abfb45999959e8f3991ec77d/Untitled%2030.png

Laya%20Native%20%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90%E5%92%8C%E6%94%B9%E9%80%A0%E7%AC%94%E8%AE%B0%2067e77270abfb45999959e8f3991ec77d/Untitled%2031.png

对比两种二进制,在laya 是否卸载方面内存变化结论

很明显能够看到在切换场景的时候内存有一个很深的释放,因为在释放完成第一个layacontroller之后会重新建立一个新的layaController , 这里会有一个很深的尖角,因为需要载入新的场景和资源,内存会再次上升。因为小游戏在运行时候必须的内存大小基本固定,所以会回归到正常的内存数值。

现有Laya 单利方式在内存表现方面没有太多的增加,在大厅进入到录播AI课的时候会有一定的内存上涨,在从录播AI课回到大厅的时候,一些内存没有释放掉,维持在 396M 左右, 在使用释放laya 的方式,在回到大厅时候,维持大厅刚进入时候的内存值,即 349M 左右。

有一个明显不一样的地方是,如果在新版本中大厅切换到一个空的页面,即无laya 运行时环境,iOS 内存能够从 349M 下降到 85M 左右(如下图), 但是现有的版本,内存无法下降

Laya%20Native%20%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90%E5%92%8C%E6%94%B9%E9%80%A0%E7%AC%94%E8%AE%B0%2067e77270abfb45999959e8f3991ec77d/Untitled%2032.png

旧版本如下:

Laya%20Native%20%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90%E5%92%8C%E6%94%B9%E9%80%A0%E7%AC%94%E8%AE%B0%2067e77270abfb45999959e8f3991ec77d/Untitled%2033.png

具体时机数值比较

Untitled

Laya单独一个课件载入到一个新的demo 中

Laya%20Native%20%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90%E5%92%8C%E6%94%B9%E9%80%A0%E7%AC%94%E8%AE%B0%2067e77270abfb45999959e8f3991ec77d/Untitled%2034.png

在正常运行时候 191M (这里没有声网,没有录播课中的视频,只是laya 课件), 但是卸载以后,就在 70.3M

Laya%20Native%20%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90%E5%92%8C%E6%94%B9%E9%80%A0%E7%AC%94%E8%AE%B0%2067e77270abfb45999959e8f3991ec77d/Untitled%2035.png

就是理论评估,本讲课件暂用内存在 121M 左右。

在学生端这里,从大厅进入—>到AI录播课→回到大厅流程 (在释放laya 对象时候进行延时处理,延时为 5秒 ,可以详细的记录内存的变化)

Laya%20Native%20%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90%E5%92%8C%E6%94%B9%E9%80%A0%E7%AC%94%E8%AE%B0%2067e77270abfb45999959e8f3991ec77d/Untitled%2036.png

Laya%20Native%20%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90%E5%92%8C%E6%94%B9%E9%80%A0%E7%AC%94%E8%AE%B0%2067e77270abfb45999959e8f3991ec77d/Untitled%2037.png

327 - 137 = 190M (相比上面单独demo 中的 121M ,应该算是合理的)

Laya%20Native%20%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90%E5%92%8C%E6%94%B9%E9%80%A0%E7%AC%94%E8%AE%B0%2067e77270abfb45999959e8f3991ec77d/Untitled%2038.png

大厅课件占用内存 353 - 137 = 216 M

关于性能方面初步结论:

在同等环境下,运行时候需要的内存是一致的,但是在于卸载laya 到原生端这里,2.1.0

版本因为laya 引擎自身原因,引擎运行分配出来的内存和JavaScript context,资源内存并没有被释放。导致再次拉起Laya 工程,内存会重新分配,出现严重的内存泄漏。2.11.0 版本,修改以后是可以释放掉laya运行时内存,不会因为多次释放和重新拉起引起内存泄漏。

渲染侧:

暂时还需要低端机器和相关测试环境对比

内存比对

比如线上debug 版本,进入ai课执行空游戏,即载入空场景表现

redirect url 之后内存维持在 169M

Laya%20Native%20%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90%E5%92%8C%E6%94%B9%E9%80%A0%E7%AC%94%E8%AE%B0%2067e77270abfb45999959e8f3991ec77d/Untitled%2039.png

如果使用卸载Laya 方式

Laya%20Native%20%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90%E5%92%8C%E6%94%B9%E9%80%A0%E7%AC%94%E8%AE%B0%2067e77270abfb45999959e8f3991ec77d/Untitled%2040.png

Laya%20Native%20%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90%E5%92%8C%E6%94%B9%E9%80%A0%E7%AC%94%E8%AE%B0%2067e77270abfb45999959e8f3991ec77d/Untitled%2041.png

相当于如果使用修改后的引擎,在不使用Laya 的环境中 相比之前稳定在 169M 的内存, 能够减少 169 - 82.6 = 86.4M , 也就是说可以多出来 86.4M 内存提供给额外业务使用。

后续升级:

现有iOS升级可更改的内容非常少,主要修改点在于:

  1. iOS 之前单利方式的Laya Controller 更改为对象方式,在需要的时候载入初始化,不需要的时候可以销毁。
  2. 替换新版本 laya 原生二进制文件(必要)
  3. JavaScript 部分,修改或者替换apploader.js ,index.js 新增 webglPlus.js

新版本特性:

  1. 在新版本Laya 代码中使用的是 WkWebview,iOS 可以使用高版本二进制进行新项目提审。
  2. 支持内存释放,可以做多runtime 引擎之间切换。
  3. 具备扩展性,可以在现有基础上对引擎底层做特定修改。

leetcode 09 反转链表

一道比较有意思的题,也觉得是链表里面需要掌握的一道题

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

示例 1:

输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
示例 2:

输入:head = [1,2]
输出:[2,1]
示例 3:

输入:head = []
输出:[]

提示:

链表中节点的数目范围是 [0, 5000]
-5000 <= Node.val <= 5000

进阶:链表可以选用迭代或递归方式完成反转。你能否用两种方法解决这道题?

作者:力扣 (LeetCode)
链接:https://leetcode-cn.com/leetbook/read/linked-list/f58sg/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public:
ListNode* reverseList(ListNode* head) {
//因为要返回链表,所以我们先搞一个需要返回的链表指针,初始值先置为空
ListNode * pReturn = nullptr;
ListNode * p = head; // 构造另外一个指针,用来标记当前移动指针的位置,也为了持有链表后续的值
while(p != nullptr) // 链表操作,一般就是while 语句,判空即可,一个节点一个节点的向下遍历
{
ListNode * tmp = p->next; // 这里需要有一个指针,持有当前操作指针的下一位,因为当前操作指针要被挪出去,后面信息需要保留
p->next = pReturn; // 在原始长链中移除的指针,需要插入到返回链表的头结点,所以这里是插入操作。
pReturn = p; // 原始指针更新当前指向位置,完成了返回链表的插入
p = tmp; // 当前工作指针就更新为工作指针的后一位,就是之前保存的原始链表的后续内容
}
//最后返回我们已经逆序完成的链表
return pReturn;

}
};

leetcode 08 大数相加

就最近遇到一个比较有特点的题,特点大数这个东西第一次听说这么搞。

给定两个字符串形式的非负整数 num1 和num2 ,计算它们的和。

提示:

num1 和num2 的长度都小于 5100
num1 和num2 都只包含数字 0-9
num1 和num2 都不包含任何前导零
你不能使用任何內建 BigInteger 库, 也不能直接将输入的字符串转换为整数形式

因为给出的大数是 字符串,考虑到需要逆序,比较容易的方式在自己了解不多的语言中认为 Python 比较好用,因为字符串逆序和字符串分割为数组会比较简单。于是就这么干了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Solution:
def addStrings(self, num1: str, num2: str) -> str:
#翻转 总是要每一位相加的,相加也需要有进位,从最低位到最高位
num_revert_1 = num1[::-1]
num_revert_2 = num2[::-1]
len_1 = len(num1)
len_2 = len(num2)
#取出最大长度数组和最短长度
max_len = len_1 if len_1 > len_2 else len_2
mini_len = len_1 if len_1 < len_2 else len_2

max_str_rever = num_revert_1 if max_len == len_1 else num_revert_2
mini_str_rever = num_revert_1 if mini_len == len_1 else num_revert_2
# 如果长度相等,那么无所谓长短
if max_len == mini_len:
max_str_rever = num_revert_1
mini_str_rever = num_revert_2

# 相加 给一个返回值用于返回,初始值为空
return_str = ""
need_add = 0 # 标识位,用来标识当前相加是否需要进位
for i in range(max_len):
# 每次相加的数字暂存 , 当数组长度不一致时候,在比较小的长度时候是两个数组相加 , 超过了这个长度就是最大长度的数组自身数值
tmp_add = (int(max_str_rever[i]) + int(mini_str_rever[i])) if (i < mini_len) else (int(max_str_rever[i]))
# 其实tmp_add 基本就差不多能决定下一次是否需要进位了 , 可是别忘了还有进位 , 因为进位是个位,所以下面怎么写都行 tmp_add 不可能大于18 , need_add 不可能大于 1
second_add = tmp_add % 10 + need_add # 加上进位之后的个位数, 也有可能是 10 比如 tmp_add = 9 , need_add = 1
return_str += str(second_add % 10) # 所以取余 个位数
need_add = 1 if (second_add >= 10 or tmp_add >= 10) else 0 # 如果 second_add >= 10 或者 第一次相加都 >= 10 那么就进位
#如果到最后发现进位标识符还是 1, 那么最高位再补一个 1
if need_add == 1:
return_str += '1'
#最后内容翻转就是我们需要输出的
return return_str[::-1]

leetcode 总结 01 两数之和

经历了非常严重的一整年加班以后,开始想着不要继续碌碌无为了。算法这些东西,不管是需要找工作临时练手熟悉,或者是学习,总是要有的。

废话不多说。

两数之和

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。

你可以按任意顺序返回答案。

示例 1:

输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
示例 2:

输入:nums = [3,2,4], target = 6
输出:[1,2]
示例 3:

输入:nums = [3,3], target = 6
输出:[0,1]

提示:

2 <= nums.length <= 103
-109 <= nums[i] <= 109
-109 <= target <= 109
只会存在一个有效答案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
// 两数之和 , 一个数组 , 一个 之和 , 那么 总和减去当前数值 , 就是需要查找的一个数字 。查看这个数字是否在于这个数组中
// 思路:用一个表用于查询第二个数值是否存在, 并且能取出第二个数值在提供数组中的位置 ,那么方便点儿就是 map了 。 key 作为数值 ,value 是索引
// 构建一个哈希表,用于存放数据和查询 unordered_map 不需要排序
unordered_map<int, int> hashtable;
// 一次遍历,从头开始
for(int i = 0; i < nums.size();i++)
{
// 遍历器查询缺值是否存在当前哈希表中
auto it = hashtable.find(target - nums[i]);
// 如果存在
if(it != hashtable.end())
{
// 返回,哈希表中存放的匹配值的位置,和当前位置
return {it->second,i}; // 注意返回值类型
}else
{
// 如果不存在,那么放入当前的值和index
hashtable[nums[i]] = i; // 第一位是数字 ,第二位是在数组中的位置
}
}
return {}; // 查不到肯定返回为空
}
};

leetcode 总结 02 只出现一次的数字

只出现一次的数字

给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

说明:

你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?

示例 1:

输入: [2,2,1]
输出: 1
示例 2:

输入: [4,1,2,1,2]
输出: 4

作者:力扣 (LeetCode)
链接:https://leetcode-cn.com/leetbook/read/top-interview-questions/xm0u83/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 最开始考虑是不是先要排序,然后相邻的进行比较 , 但是不需要额外空间这个要求就一定会有诡异,所以查了一下解题技巧,果然很有一套
// 技巧就是 0 异或 任何数字 都是另外的数字
// 相同的数字异或本身 刚好为 0
// 那么 数组中的每一位都进行异或运算,得出的就是出现单次的数字
class Solution {
public:
int singleNumber(vector<int>& nums) {
int singleNum = 0; // 定义需要return 的数字, 先赋值为 0
for(int i =0;i < nums.size();i++ ) // 根据长度进行一次遍历
{
singleNum = singleNum ^ nums[i]; // 执行异或逻辑
}
return singleNum; // 返回需要的单一数字
}
};