使用反射对性能造成的影响分析报告

2766次阅读  |  发布于5年以前

最近我们分析了大量的应用,并发现了许多影响 App 性能的原因,从这篇博文开始,我会一个一个地介绍我们的发现

不论是 Java 开发还是 Android 开发,反射都是非常好用的工具,但反射同时也是影响 Android 应用性能的一大原因,下面就讲两个例子让大家了解反射的坏处吧:

两个真实的例子

第一个例子就是 NYTimes Android App 了。在 NimbleDroid 的帮助下,NYTimes 的程序猿发现 Gson 中使用的反射型 Adapter 在应用启动时增加了大约 700ms 的延迟,最终他们通过手动地适配每一种数据类型对应的 Adapter 解决了这个问题。

第二个例子就是 Photobucket,Photobucket 是一个大型的图片分享平台,而 Photobucket 的程序猿在开发时使用的反射技术也给他们带来了一些性能瓶颈。


调用 com.photobucket.api.client.jersey.UserClient 构造器需要 660ms

com.photobucket.api.client.jersey.UserClient 构造器需要 660ms 才能执行完成,不妨细看该柱状图,我们会发现大部分开销都来自于反射:

构造方法中有大量的反射调用,例如:java.lang.Class.getGenericInterfaces

需要提醒的是:getGenericInterfaces() 返回类具体实现的接口类型,在该构造方法中,它被调用了5次,大约需要81ms。81ms看起来好像不多,但所有的反射调用加起来可就差不多 600ms了,不妨看看为什么需要这么多时间。

从源码上看,该库似乎允许开发者通过注解配置 REST 客户端,问题就是:该库没有在编译期处理注解,而是在运行时解析和创建 REST 客户端(利用反射)。从性能的角度看,这就是灾难性的打击。

微基准

下面我们创建一个简单的例子以测试反射到底有多慢。

例子中使用的类是 Activity,然后重复执行某个操作大约10000次,具体代码如下:

Class<?> clazz = android.app.Activity.class;
for (int i = 0; i < 10000; i++) {
    clazz.getFields();
}

此外,我们还有关于创建对象的测试(DummyItem 是一个空类),以了解反射创建对象的开销:

try {
    for (int i = 0; i < 1_000_000; i++) {
        DummyItem.class.newInstance();
    }
} catch (InstantiationException e) {
    e.printStackTrace();
} catch (IllegalAccessException e) {
    e.printStackTrace();
}

下面是测试的结果(下面的数值单位都是 ms,而且都是真实设备测试的):

操作 NEXUS 5 (6.0) ART GALAXY S5 (5.0) ART GALAXY S3 mini (4.1.2)Dalvik
getFields 1108 1626 27083
getDeclaredFields 347 951 7687
getGenericInterfaces 16 23 2927
getGenericSuperclass 247 298 665
makeAccessible 14 147 449
getObject 21 167 127
setObject 21 201 161
createDummyItems 312 358 774
createDummyItemsWithReflection 1332 6384 2891

很显然反射在 Android 中真的非常慢,使用反射的时间开销是(1332ms, 6384ms, 2891ms),没使用反射的时间开销是(312ms, 358ms, 774ms)。有趣的是,Android 5.0 使用的 ART 虚拟机在更好的设备上使用反射的性能甚至比 Android 4.0 使用的 Dalvik 在更老的设备上要差。而在 Android 6.0 ART 上性能表现就好很多,但开销还是很高。

更多的实例

ActiveAndroid 是一个使用反射实现的库,不妨分析应用市场中使用它的应用,看看它对应用启动时间的影响:

这是 Scribd 的性能表现:

com.activeandroid.ActiveAndroid.initialize 调用开销是 1093ms

Myntra 也出现了一样的问题:

com.activeandroid.ActiveAndroid.initialize 调用开销是 1421ms

如你所见,该库需要超过1秒的时间以进行初始化,考虑到用户期待应用的启动时间的平均值为2秒,这个时间真的很多……

总的来说,反射在 Android 终点性能表现真的不佳,为了尽可能给用户提供平滑的体验,我们建议:

建议:避免使用反射(或者使用反射的库),特别是,别使用反射型 Adapter 去序列化 Java 对象。

Copyright© 2013-2019

京ICP备2023019179号-2