生成式AI领域的最新成果都在这里!抢 QCon 展区门票 了解详情
写点什么

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:201337

评论 2 条评论

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

Spring 架构

小万哥

Java spring 后端 springboot SpringCloud

苹果Mac软件KeyShot 2023.2 Pro 高级渲染和动画制作,KeyShot 2023.2 Pro最新激活完整版教程

晴雯哥

功能更新 | FinClip 7月产品大事记

FinClip

倒计时 1 天!浙江大学、乘云数字、阿里云等多位技术专家教授解读最新运维技术

OpenAnolis小助手

Meetup 龙蜥活动

新利好带动 POSE 持续上扬,月内几近翻倍

威廉META

新利好带动 POSE 持续上扬,月内几近翻倍

EOSdreamer111

2023 年最新、最全、最实用的 Java 岗面试真题,已收录 GitHub

采菊东篱下

程序员 java面试

新利好带动 POSE 持续上扬,月内几近翻倍

大瞿科技

14. 练习:登录注册系统

茶桁

Python 练习 登录注册

百度刘林:渗透深层企业经营 AIGC将为千行百业带来变革机会

百度Geek说

人工智能 百度 AIGC 企业号 8 月 PK 榜

SpringBoot3分库分表

Java 架构 springboot SpringBoot3

山东布谷科技直播平台搭建游戏开发技术分享:数据存储的重要意义

山东布谷科技

软件开发 游戏 数据存储 源码搭建 直播平台搭建

储能pcb的布局注意事项与制造难点

华秋电子

资讯速递 | ArkUI-X 预览版已正式开源!

OpenHarmony开发者

OpenHarmony

Hutool:一行代码搞定数据脱敏 | 京东云技术团队

京东科技开发者

Jackson hutool 数据脱敏 企业号 8 月 PK 榜

新利好带动 POSE 持续上扬,月内几近翻倍

鳄鱼视界

新利好带动 POSE 持续上扬,月内几近翻倍

股市老人

Programming abstractions in C阅读笔记:p91-p106

codists

瓴羊Quick BI报表工具助企业洞察市场趋势,让数据说话

对不起该用户已成仙‖

小程序生成App:可跨平台开发的移动应用开发框架

没有用户名丶

麒麟云体验优化之Pod列表展示优化

麒麟云

Kubernetes 云原生 容器云 银河麒麟 云原生操作系统

全彩LED电子屏的驱动器有哪几种?

Dylan

电压电流 驱动 恒流源输出电路 LED显示屏 全彩LED显示屏

揭秘华为云GaussDB(for Redis)丨大key治理

华为云开发者联盟

数据库 后端 华为云 华为云开发者联盟 企业号 8 月 PK 榜

【Linux】云服务器自动化部署VuePress博客(Jenkins)

ReturnTmp

jenkins vuepress

Falco操作系统安全威胁监测利器

统信软件

安全 操作系统

基金公司数据治理实践,打造“点线面体”的数据治理体系

袋鼠云数栈

数据中台 数字化转型 数据治理 基金 数据中台数据治理

Downie 4.6.24 支持多网站视频下载工具for Mac ,一键直装永久使用

晴雯哥

为MySQL新增一张performance_schema表 | StoneDB 技术分享会 #4

StoneDB

MySQL 数据库 HTAP StoneDB

Royal TSX for Mac(远程管理软件) v5.1.2激活版

mac

苹果mac Windows软件 Royal TSX 远程连接管理工具

陪跑多年,Postgres终崛起封王!

这我可不懂

数据库 postgresql postgres

字节跳动基于火山引擎DataLeap的一站式数据治理架构实践

字节跳动数据平台

大数据 数据中台 企业号 8 月 PK 榜

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