iOS 性能优化 - Allocations分析内存分配

2018 年 11 月 19 日 CocoaChina

本公众号内容均为本号转发,已尽可能注明出处。因未能核实来源或转发内容图片有权利瑕疵的,请及时联系本号,本号会第一时间进行修改或删除。 QQ : 3442093904 


Allocations

Allocations用来分析静态内存分配。


Demo项目


Demo App

Demo是一个简单的图片应用:首页只有一个简单的入口;次级页面会读取本地图片,加滤镜,然后按照瀑布流的方式显示出来;第三个页面提供大图显示;




运行代码:点击Photos -> 进入main -> 点击一张图,进入详情页 -> Pop直到回到第一个页面,重复这段操作,从XCode的内存模块发现两个问题。

  1. 内存峰值过大

  2. 退出界面后,内存没有降低。



Statistics

菜单栏选择Product -> Profile,然后选择Allocations,运行项目,按照内存飙升的路径重复操作,采集到内存数据后停止运行。


默认看到的数据是Statistics(静态分析),点击mark 2可以更改数据分析模式。


为了更好的解决问题,有必要讲解下这里的内存相关概念:


  • All Heap Allocation 堆上malloc分配的内存,不包过虚拟内存区域。

  • All Anonymous VM 匿名的虚拟内存区域。何为匿名呢?就是Allocations不知道是你哪些代码创建的内存,也就是说这里的内存你无法直接控制。像memory mapped file,CALayer back store等都会出现在这里。这里的内存有些是你需要优化的,有些不是。


表格的每一列的数据解释:


我们勾选前四个Graph,通过曲线的趋势,不难看出问题就出在VM CoreImage上:

我们点一下mark 4,进入详情页,然后选择一个内存对象地址,在右侧我们可以看到这个对象是如何被创建的:


双击这一行,进入汇编界面,可以看出来,最后内存是由mmap分配的:


也可以双击右侧Stack Trace,看看自己的代码:


Generation

利用Generation,我们可以对内存的增量进行分析:时间戳B相比时间戳A有那些内存增加了多少


点最下面的Mark Generation会创建一个Generation,并且在图形区域增加一个小红旗。


点Statictis,从下拉列表中,选择Generations,我们就可以看到内存增量主要在VM: CoreImage中,这里的每一行也可以点击查看详情,或者在右侧查看栈:


Call Tree

按照类似的方式,这次我们选择Call Tree来直接分析代码是如何创建内存的,勾选Separator By Thread,按照线程来对内存进行分类:


Tips:按住Option,然后鼠标左键点击图中箭头指向的三角箭头,可以快捷展开调用栈。


也可以勾选:

  • Invert Call Tree 倒置函数栈

  • Hide System Libraries 隐藏系统库


这样,我们过滤掉了系统符号,同时也用更直观的方式看到了调用栈:


我们同样可以选择一行双击,然后可以进入XCode查看源代码或者汇编代码:

可以看到,引起内存爆增的就是这段代码

- (UIImage *)filterdImage:(UIImage *)originalImage{
    CIImage *inputImage = [CIImage imageWithCGImage:originalImage.CGImage];
    CIFilter *filter = [CIFilter filterWithName:@"CIColorMonochrome"];
    [filter setValue:inputImage forKey:kCIInputImageKey];
    [filter setValue:[CIColor colorWithRed:0.9 green:0.88 blue:0.12 alpha:1] forKey:kCIInputColorKey];
    [filter setValue:@0.5 forKey:kCIInputIntensityKey];
    CIContext *context = [CIContext contextWithOptions:nil];
    CIImage *outputImage = filter.outputImage;
    CGImageRef image = [context createCGImage:outputImage fromRect:outputImage.extent];
    UIImage * filterImage =  [UIImage imageWithCGImage:image];
    return filterImage;
}


Allocations list

Allocations List提供了一种更纯粹的方式,让你看到内存的分配的列表,我们一般会选择内存从高到低,看看是不是有什么意外分配的大内存块:


可以看到,排名前几的内存块都是VM:CoreImage,从名字也就不难看出来,这是图片引起的内存。

我们选中某一行,在右侧可以看到具体的调用栈:


解决内存问题

不管是上述那种分析方式,我们都很容易找到问题出现在这段代码里:


那么,为什么这段代码分配内存后没有释放呢?如果有一些CoreFoundation或者CoreGraphics经验,很容易就知道这里应该手动释放内存,这里假设你不知道,那么怎么找到原因呢?


看看这个函数的文档文档就知道了:

- (nullable CGImageRef)createCGImage:(CIImage *)image
                            fromRect:(CGRect)fromRect


Creates a Quartz 2D image from a region of a Core Image image object.

Renders a region of an image into a temporary buffer using the context, then creates and returns a Quartz 2D image with the results.

You are responsible for releasing the returned image when you no longer need it.


所以,在创建函数后,我们进行release即可

CGImageRef image = [context createCGImage:outputImage fromRect:outputImage.extent];
UIImage * filterImage =  [UIImage imageWithCGImage:image];
CGImageRelease(image);


再观察内存:


可以看到,仍然有个峰值,我们的瀑布流界面其实并不需要完整的大图塞给ImageView,一个比较常见的优化方式是对图片进行缩放,这里有两点要注意


  1. 大图的缩放不要用UIGraphicsBeginImageContextWithOptions或者CGBitmapContextCreate,因为当图片很大的时候,这个函数很有可能创建几百M甚至上G的内存,应该用更底层的ImageIO相关的API

  2. 假如ImageView的尺寸是100*100,那么为了不影响用户体验,你应该缩放到100*UIScreem.main.scale


缩放举例:

- (UIImage *)scaledImageFrom:(NSURL *)imageUrl width:(CGFloat)width{
    CGImageSourceRef source =  CGImageSourceCreateWithURL((__bridge CFURLRef)imageUrl, nil);
    CFDictionaryRef options = (__bridge CFDictionaryRef) @{
                                                           (id) kCGImageSourceCreateThumbnailWithTransform : @YES,
                                                           (id) kCGImageSourceCreateThumbnailFromImageAlways : @YES,
                                                           (id) kCGImageSourceThumbnailMaxPixelSize : @(width)
                                                           };

    CGImageRef scaledImageRef = CGImageSourceCreateThumbnailAtIndex(source, 0, options);
    UIImage *scaled = [UIImage imageWithCGImage:scaledImageRef];
    CGImageRelease(scaledImageRef);
    return scaled;
}


作者:黄文臣

链接:https://blog.csdn.net/Hello_Hwc/article/details/83241475


相关推荐:


登录查看更多
1

相关内容

虚拟内存是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。与没有使用虚拟内存技术的系统相比,使用这种技术的系统使得大型程序的编写变得更容易,对真正的物理内存(例如RAM)的使用也更有效率。
【2020新书】实战R语言4,323页pdf
专知会员服务
100+阅读 · 2020年7月1日
【2020新书】使用高级C# 提升你的编程技能,412页pdf
专知会员服务
57+阅读 · 2020年6月26日
深度神经网络实时物联网图像处理,241页pdf
专知会员服务
76+阅读 · 2020年3月15日
TensorFlow Lite指南实战《TensorFlow Lite A primer》,附48页PPT
专知会员服务
69+阅读 · 2020年1月17日
【电子书】C++ Primer Plus 第6版,附PDF
专知会员服务
87+阅读 · 2019年11月25日
Linux挖矿病毒的清除与分析
FreeBuf
14+阅读 · 2019年4月15日
使用 C# 和 Blazor 进行全栈开发
DotNet
6+阅读 · 2019年4月15日
支持多标签页的Windows终端:Fluent 终端
Python程序员
7+阅读 · 2019年4月15日
文本分析与可视化
Python程序员
9+阅读 · 2019年2月28日
DiscuzX 3.4 Phar反序列化漏洞
黑客工具箱
8+阅读 · 2019年1月4日
已删除
AI科技评论
4+阅读 · 2018年8月12日
优化哈希策略
ImportNew
5+阅读 · 2018年1月17日
iOS高级调试&逆向技术
CocoaChina
3+阅读 · 2017年7月30日
Spark的误解-不仅Spark是内存计算,Hadoop也是内存计算
Arxiv
21+阅读 · 2019年8月21日
Music Transformer
Arxiv
5+阅读 · 2018年12月12日
Arxiv
6+阅读 · 2018年4月24日
Arxiv
7+阅读 · 2018年3月21日
VIP会员
相关资讯
Linux挖矿病毒的清除与分析
FreeBuf
14+阅读 · 2019年4月15日
使用 C# 和 Blazor 进行全栈开发
DotNet
6+阅读 · 2019年4月15日
支持多标签页的Windows终端:Fluent 终端
Python程序员
7+阅读 · 2019年4月15日
文本分析与可视化
Python程序员
9+阅读 · 2019年2月28日
DiscuzX 3.4 Phar反序列化漏洞
黑客工具箱
8+阅读 · 2019年1月4日
已删除
AI科技评论
4+阅读 · 2018年8月12日
优化哈希策略
ImportNew
5+阅读 · 2018年1月17日
iOS高级调试&逆向技术
CocoaChina
3+阅读 · 2017年7月30日
Spark的误解-不仅Spark是内存计算,Hadoop也是内存计算
相关论文
Arxiv
21+阅读 · 2019年8月21日
Music Transformer
Arxiv
5+阅读 · 2018年12月12日
Arxiv
6+阅读 · 2018年4月24日
Arxiv
7+阅读 · 2018年3月21日
Top
微信扫码咨询专知VIP会员