写点什么

FaceBook 安卓 Feed 流的内存优化实践

  • 2019-09-20
  • 本文字数:3002 字

    阅读完需:约 10 分钟

FaceBook安卓Feed流的内存优化实践

大量的用户每天在 Android 设备上使用 Facebook,滚动新闻 Feed 流页面,包括个人资料,活动,页面和组,与他们关心的人员和信息进行互动等一系列行为。 所有这些不同的 Feed 类型都由 Android Feed Platform 小组创建的平台提供支持,因此我们对 Feed 平台进行的任何优化都可能提高我们的应用程序的性能。 我们专注于页面的滚动性能,因为我们希望用户在滚动他们的 Feed 流页面时有一个平滑的体验。

为了帮助我们实现这一点,我们有几种自动化工具,可以跨不同的场景和不同的设备在 Feed 平台上运行性能测试,测量代码在运行时内存使用,帧速率等方面的运行情况。 其中一个工具 Traceview 显示了我们的程序对 Long.valueOf ()函数的调用次数相对较多,这导致对象在内存中累积并导致应用程序卡顿停止等。 这篇文章描述这个问题,我们权衡了各种潜在解决方案之后,对改进 Feed 流平台而进行的一系列优化。

便利性带来的缺点

我们从 Traceview 的一个方法分析报告中注意到:facebook 的 app 对 Long.valueOf ()函数的大量调用。之后,我们进行了又进一步的测试,证实了当我们滚动新闻列表时,Long.valueOf ()方法的调用会意外升高。



当我们查看堆栈时,我们发现这个函数没有被直接的从 Facebook 的代码调用,而是隐式地由编译器插入的代码。 在分配长整型对象的原始长值时调用此函数。 Java 支持对象和原始的简单类型(例如,整数,字符),并提供了一种在它们之间无缝转换的方式。 这种方式称为自动装箱,因为它将基本类型装箱为相应的类型的对象类型。 虽然这是一个方便的开发功能,但是它同时也创建了开发人员不知道的新对象。



在对一个示例应用程序的堆栈中发现 Long 对象有大量的存在; 虽然每个对象本身都不大,但是存在的大量的 Long 对象占据了应用程序在堆中的大部分内存。 对于运行 Dalvik 的设备来说,会有很大的影响。 与 Android 的 ART 运行时环境不同,Dalvik 没有分代式垃圾回收机制 ,造成很多小对象的垃圾回收效率很低。 当我们滚动新闻 Feed 流,会造成 Long 对象数量增加,垃圾收集将导致应用程序卡顿来从内存中清除未使用的对象。 积累的对象越多,垃圾收集器将越来越频繁地暂停应用程序,导致卡顿使得户体验不佳。


幸运的是,Traceview 和 Allocation Tracker 等工具可以帮助我们找到这些函数调用的位置。 在查看了这些自动装箱事件的根源之后,我们发现大多数成因都是:将 Long 类型的基本类型数据插入 HashSet 数据结构中造成。 (我们使用这个数据结构存储新闻 Feed 的哈希值,稍后检查某个哈希是否已经在 Set 中。)HashSet 提供对具体 feed 的快速访问。 由于哈希计算并存储在一个原始的长变量中,然而我们的 HashSet 仅适用于对象,所以当调用 set.put(Hash )时,我们会得到不可避免的自动装箱。


作为一个解决方案,可以使用基本数据类型而不是对象类型的 Set 实现,但是结果并不像我们预期的那么简单。

目前的解决方案

有几个现有的 Java 库为原始数据类型提供了 Set 实现。 几乎所有这些类库都是 10 多年前创建的,当时在移动设备上运行的唯一的 Java 是 J2ME。为了确定可行性,我们需要在 Dalvik / ART 下进行测试,并确保它们在资源更受限的移动设备上表现良好。 我们创建了一个小型测试框架来帮助将这些库与现有的 HashSet 进行比较。 结果表明,这些库中的一些库具有比 HashSet 更快的运行时间,并且具有较少的 Long 对象,但是它们仍然在内部分配了很多 Long 对象。 例如,Troow 库中的一部分 TLongHashSet 在测试时分配了大约 2 MB 的对象,共有 1,000 个 item



对其他的类库进行测试,包括 PCJ 和 Colt, 显示了类似的结果。


现有的解决方案不符合我们的需求。 我们考虑是否可以创建一个新的 Set 实现,并针对 Android 进行优化。 在 Java 的 HashSet 中,使用单个 HashMap 来实现一个相对简单的实现。



向 HashSet 添加新 item 意味着将其添加到内部 HashMap,其中对象是关键字,而 HashSet 的实例是该值。 要检查对象成员身份,HashSet 将检查其内部 HashMap 是否包含对象作为键。 可以使用 Android 优化的 map 和相同的原则来实现 HashSet 的替代方案。

引进 LongArraySet

你可能已经熟悉了 LongSparseArray,它是 Android 支持类库中的一个类,用作使用 long 类型作为 key 的 map。 使用示例



LongSparseArray 的工作方式与 HashMap 不同。 当调用 mapHashmap.get(KEY5 )时,下图说明了如何在 HashMap 中找到该值:



当使用 HashMap 上的键检索值时,它使用密钥的哈希值作为索引访问数组中的值,即 O(1)时间复杂度的的直接访问。 对 LongSparseArray 进行相同的调用如下所示:



LongSparseArray 使用二分搜索,运行时间为 O(log N )的时间复杂度操作搜索排序密钥数组的密钥值。 数组中的键的索引值用于查找 values 数组中的值。


HashMap 分配一个大数组,以避免 hash 冲突,但是这样导致搜索速度较慢。 LongSparseArray 分配两个小数组,使其内存占用更小。 但是为了支持其搜索算法,LongSparseArray 需要在连续的内存块中分配其内部数组。 添加更多的 item 将需要在当前空间不足的情况下分配新的数组。 LongSparseArray 的工作原理使得它在保存超过 1,000 个项目时效率下降,这些差异对性能有更重要的影响。(您可以在官方文档中了解有关 LongSparseArray 的更多信息,并通过观看 Google 的简短视频。)


由于 LongSparseArray 的键是原始 long 类型,所以我们可以使用与 HashSet 相同的方法创建一个数据结构,使用 LongSparseArray 作为内部映射而不是 HashMap。

建立 LongArraySet

新的数据结构更加合理。通过使用与之前相同的测试框架,我们将新的数据结构与 HashSet 进行了比较。 每个数据结构都通过添加 X 个 item 进行测试,检查每个 item 的存在,然后删除所有 item。 我们使用不同数量的 item(X = 10,X = 100,X = 1,000 …)运行测试,并平均每个 item 完成每个操作所花费的时间。


运行时结果(时间显示为纳秒):



我们看到使用新数据结构的 contains 和 delete 方法的运行时效率改进。 另外,随着数组中 item 数的增加,添加新 item 花费更多时间。 这与我们已经知道的 LongSparseArray 是一致的 ,当 item 数量超过 1,000 时,它与 HashMap 的表现不一样。 在我们的用例中,我们只处理了数百个 item,所以这是一个我们愿意做的权衡。


我们也看到了内存使用有很大的改善。 在查看堆转储和分配跟踪报告时,我们注意到对象分配的减少。 下面是当添加 1,000 个 item 进行 20 次迭代时,HashSet 和 LongArraySet 实现的并行分配报告:



除了避免所有 Long 对象分配之外,LongSparseArray 更具有内存效率,在这种情况下的分配减少了约 30%。

结语

通过了解其他数据结构如何工作,我们能够为我们的需求创建一个更优化的数据结构。 垃圾收集器必须工作的越少,这样丢帧的可能性就越低。 使用新的 LongArraySet 类和类似的 IntArraySet 作为原始 int 数据类型,我们能够在整个应用程序中减少大量的对象内存分配。


这个案例研究表明了我们选择数据结构的重要性。虽然这种解决方案对于所有用例来说并不完美,因为这种实现对于非常大的数据集来说较慢,但是还可以继续对我们的代码进行优化。


你可以在下面网址找到两个数据结构的源代码。 我们很高兴继续努力应对挑战,优化我们的 Feed 平台,并与社区分享我们的解决方案。


本文转载自公众号贝壳产品技术(ID:gh_9afeb423f390)。


原文链接:


https://mp.weixin.qq.com/s/naZe4cXcXfe9cXQPLoZf9g


2019-09-20 17:201492

评论 2 条评论

发布
用户头像
类似的性能优化,最关键的是一套行之有效的检测方案,之后才是解决方案,或者说二者也可能是相辅相成
2020-03-19 01:02
回复
用户头像
大数据或者大流量下确实很多都需要优化
2020-02-12 17:47
回复
没有更多了
发现更多内容

阿里巴巴为什么不建议直接使用@Async注解?

Java你猿哥

Java ssm java8 Async Java工程师

Spring知识点总结!已整理成142页离线文档(源码笔记+思维导图)

三十而立

Java

弯道超车!阿里高工新产Java面试速成指南,面试骚操作都在里面了

Java你猿哥

Java 面试 面经 Java工程师 春招

MySQL 语句中 where 条件后为什么写上1=1 , 是什么意思?

Java你猿哥

Java MySQL sql 后端 ssm

是找茬? 还是装 B?阿里面试每轮必问的“Spring Boot”意义何在?

三十而立

马鞍山等级测评机构有哪些?有几家?在哪里?

行云管家

等保测评 等级测评 马鞍山

置顶两个月!《程序员如何向架构师转型》神作在Github持续霸榜

做梦都在改BUG

Java 程序员 系统设计 架构师

数据出境是什么意思?我国数据出境合规要求是什么?

行云管家

数据 数据安全 堡垒机 数据出境

简单的文件搜索工具:Find Any File激活版

真大的脸盆

Mac Mac 软件 文件搜索 搜索工具 搜索软件

数据库 CI/CD 工具 -- Bytebase 介绍

Se7en

最佳实践 | 用腾讯云智能语音打造智能对话机器人

牵着蜗牛去散步

腾讯云 腾讯 语音识别 语音合成 智能对话机器人

2023字节、腾讯、阿里等6家大厂Java开发面试真题+高频面试题总结

小小怪下士

Java java程序员 java面试 Java面试题

不懂就问:MySQL delete 表数据,磁盘空间为什么没有被释放?

Java你猿哥

Java MySQL 数据库 innodb Java工程师

厉害了!阿里内部都用的Spring+MyBatis源码手册,实战理论两不误

Java你猿哥

spring 面试 Spring Boot mybatis 面经

Bytebase vs Flyway

Bytebase

数据库 版本控制 变更

MySQL8.0 优化器介绍(一)

GreatSQL

MySQL greatsql greatsql社区

微服务架构下你不得不知的3种部署策略

做梦都在改BUG

Java 架构 微服务

批量上传iOS应用程序截图的实用技巧

PostgreSQL 技术内幕(六)Greenplum 排序算子

酷克数据HashData

2周时间就掌握了Spring boot,原来是收藏了这样一份文档资料

三十而立

Java spring

太厉害了!腾讯T4大牛把《数据结构与算法》讲透了,带源码笔记

Java你猿哥

Java 数据结构 算法 数据结构算法 左程云

低代码平台搭建CRM 加速重构业务模式

力软低代码开发平台

龙蜥白皮书精选:龙蜥全面支持 Intel 第四代可扩展处理器 SPR 平台

OpenAnolis小助手

开源 Spr 操作系统 intel 龙蜥社区

消费级AR眼镜爆发将近:Rokid+无影突破算力,打造“第三块屏幕”

云布道师

无影

在 Kubernetes 中部署应用交付服务(第 2 部分)

NGINX开源社区

nginx Kubernetes

Dubbo 正式支持 Spring 6 & Spring Boot 3

Java你猿哥

Java spring Spring Boot dubbo ssm

限时公开,2023 年阿里巴巴 Java 面试权威指南(全彩版)

架构师之道

Java 面试

LED透明屏私人定制势不可挡

Dylan

电子 LED显示屏 屏幕

玖章算术CEO叶正盛在杭州人工智能小镇AIGC论坛发表主题演讲

NineData

人工智能 代码开发 AIGC 玖章算术 NineData

干货分享|袋鼠云数栈离线开发平台在小文件治理上的探索实践之路

袋鼠云数栈

大数据 平台开发

什么是“语法糖”?Java中有哪些常见糖?

Java你猿哥

Java ssm Java工程师 语法糖

FaceBook安卓Feed流的内存优化实践_文化 & 方法_贝壳产品技术_InfoQ精选文章