手淘双十一521 性能优化项目揭秘
这也从侧面说明了手淘是以图片为浏览主体内容的大型应用。而往往图片需要较大的内存块,在分配时引起GC的可能性也往往最大。那我们能不能将图片这部分需要的内存移走而不在Dalvik Heap分配呢?如果能,那么不单GC会明显减少,同时Dalvik Heap总大小也会下降50%左右,对整体性能会有显著的提升。 何处安放的Pixel Data Ashmem即匿名共享内存,使用的核心过程是创建一个/dev/ashmem设备文件,控制反转设置文件的名字和大小,最终把设备符交给mmap就得到了共享内存。在Android系统中Binder进程间通信的实现就是依赖Ashmem完成不同进程间的内存共享。但此处并不利用其共享特性,而是使用它在Native Heap完成内存分配。 图片空间如何才能使用Ashmem,答案在Facebook推出的Fresco中已有提及,那就是解码时的purgeable标记,这样在系统底层解码位图时会走Ashmem空间分配,而非Dalvik Heap空间。这样就解决了像素数据存放由Dalvik到Native的问题了吗? BitmapFactory.Options options = new BitmapFactory.Options(); /* * inPurgeable can help avoid big Dalvik heap allocations (from API level 11 onward) */ options.inPurgeable = true; Bitmap bitmap = BitmapFactory.decodeByteArray(inputByteArray, 0, inputLength, options); 小心Bitmap空包弹 事实并非那么简单,最后实际解出来Bitmap没有像素数据(没有到Ashmem分配任何空间),根本没有去完成jpeg或者png解码。此时的Bitmap是个空包弹!它所做的只是把输入的解码前数据拷贝到了native内存,如果把这个Bitmap交给ImageView渲染就糟了,在View.draw()时Bitmap会在主线程进行图片解码。 而且不要天真的以为Bitmap解码一次之后再多次使用都不会引起二次解码,在系统内存紧张时底层可能回收Ashmem里这部分内存。回收后该Bitmap再次渲染时又将在主线程完成一次解码。如果就这样直接使用该机制,性能上无疑雪上加霜。 那么怎样才能避免这个隐形炸弹呢?还好SDK预留了一个C层方法AndroidBitmap_lockPixels。而lockPixels底层完成的工作大致如下图所示。第一步是prepareBitmap完成真正的数据解码,在工作线程调用AndroidBitmap_lockPixels避免了在主线程进行数据解码;第二步是完成对分配出来的Ashmem空间的锁定,这样即使在系统内存紧张时,也不会回收Bitmap像素数据,避免多次解码。 貌似解决了Bitmap渲染的所有问题,但在手淘中则不然。为了兼容低版本系统以及提升webp解码性能,我们使用了自己的解码库libwebp.so,怎样把它解码出来的数据也存放到Ashmem呢? libwebp借鸡生蛋 如果自有解码库libwebp.so要解码到Ashmem,通过SkBitmap、ashmem_create_region实现一套类似的机制是不太现实的。一方面Skia库的源码编译兼容会存在很大问题,另一方面很多系统层面的核心接口并没有对外。所以实现这点的关键还是要借助系统已经提供的purgeable到Ashmem的机制,借鸡生蛋,稳定性和成本上都能得到保证: 依据图片宽高生成空JPEG。 走系统解码接口完成Ashmem Bitmap生成。 覆写Pixel Data地址在libwebp完成解码。 更进一步,迁移解码前数据 上面谈到的内存迁移都是针对Decoded像素数据的,而Encoded图像数据在解码时会在Dalvik Heap保存一份,解码完成后再释放;Ashmem方式解码时在底层又会拷贝一份到Native内存,这份数据直到整个Bitmap回收时才释放。那能否直接将网络下载的Encoded数据存放到Native内存,省去Dalvik Heap上的开销以及解码时的内存拷贝呢? 的确可以,将网络流数据直接转移到MemoryFile可实现,但遗憾的是真机测试中发现,小米及其他国产“神机”(自改ROM),多线程使用MemoryFile获取fd到BitmapFactory解码,会出现系统死机,怀疑是在并发情况下系统代码级别的死锁造成。手机淘宝放弃了这种方案,改用ByteArrayPool复用池技术来减少Dalvik Heap针对Encoded Image的内存分配,效果也不错。如果应用能接受单线程解码,还是MemoryFile方案更具优势。 是放手的时候了 上文提到Bitmap像素数据存放到Ashmem,有读者可能担心数据回收问题,其实还是由GC来触发Ashmem内存的回收。在Dalvik层如果一个Bitmap已经不被任何地方引用,那么在下一次GC时该Bitmap就会从Ashmem中回收,大致流程示意如下图。 再看内存占用 我们再次在魅蓝note1中dump出首页滑动后的内存,如下图可以看出,原来byte数组(k)大量占用已经不存在了,Bitmap(c)与BitmapDrawable(已不在前14名当中)的占用也急骤下降。应用的总体内存下降近60%。 在双11版本上,针对一些热门机型在搜索结果页不断滚动使用,进行了不同版本的内存占用对比分析,如下图。可以看出,除华为3c和vivo这类系统内存偏小使用上一直受到控制、内存较为紧张的外,大部分机型内存的下降幅度都达到45%以上。 挠走GC之痒 内存下降不是最终目的,最终要将GC对性能的影响降到最低。仍然以魅蓝note1打开首页后滑动到底的内存堆积图来做对比。可以看到旧版本内存占用上升趋势相当明显,一路带有各式“毛刺”直奔70MB,每形成一个毛刺就意味一次GC。而双11版本中,内存只在初期有上升,而后很快下降到21MB左右,后期也显得平滑得多,没有那么多的“毛刺”,就意味着GC发生的次数在明显减少。 旧版本 双11版本 同时使用一些热门机型,针对双十一版本在首页不断滑动,进行前后版本的GC_FOR_ALLOC次数对比。热门机型GC次数下降了4~8倍,效果非常明显。 通过上文描述的各个优化方案,手机淘宝于双十一前在大部分机型上达到了521目标-Android手机内存节省50%,启动时间和页面帧率提升20%,H5页面实现1s法则。 (编辑:云计算网_泰州站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |