【发布时间】:2010-09-30 22:28:48
【问题描述】:
使用反射创建对象而不是调用类构造函数会导致任何显着的性能差异吗?
【问题讨论】:
标签: java performance optimization reflection
使用反射创建对象而不是调用类构造函数会导致任何显着的性能差异吗?
【问题讨论】:
标签: java performance optimization reflection
是的 - 绝对。通过反射查找一个类,数量级更昂贵。
引用Java's documentation on reflection:
由于反射涉及到动态解析的类型,某些 Java 虚拟机优化无法执行。因此,反射操作的性能比它们的非反射对应物要慢,并且应该避免在对性能敏感的应用程序中经常调用的代码部分中。
这是我在我的机器上用 5 分钟完成的一个简单测试,运行 Sun JRE 6u10:
public class Main {
public static void main(String[] args) throws Exception
{
doRegular();
doReflection();
}
public static void doRegular() throws Exception
{
long start = System.currentTimeMillis();
for (int i=0; i<1000000; i++)
{
A a = new A();
a.doSomeThing();
}
System.out.println(System.currentTimeMillis() - start);
}
public static void doReflection() throws Exception
{
long start = System.currentTimeMillis();
for (int i=0; i<1000000; i++)
{
A a = (A) Class.forName("misc.A").newInstance();
a.doSomeThing();
}
System.out.println(System.currentTimeMillis() - start);
}
}
有了这些结果:
35 // no reflection
465 // using reflection
请记住,查找和实例化是一起完成的,在某些情况下可以重构查找,但这只是一个基本示例。
即使您只是实例化,您仍然会受到性能影响:
30 // no reflection
47 // reflection using one lookup, only instantiating
再说一遍,YMMV。
【讨论】:
是的,它更慢。
但请记住该死的第一条规则——过早的优化是万恶之源
(好吧,可能与 DRY 的 #1 并列)
我发誓,如果有人在工作中找到我并问我这个问题,我会在接下来的几个月里非常注意他们的代码。
在确定需要它之前,永远不要优化,在此之前,只需编写好的、可读的代码。
哦,我也不是说编写愚蠢的代码。只是考虑你可能做到的最干净的方式——不要复制和粘贴等。(仍然要警惕内部循环之类的东西,并使用最适合你需要的集合——忽略这些并不是“未优化”的编程,这是“糟糕”的编程)
当我听到这样的问题时,我吓坏了,但后来我忘记了每个人都必须自己学习所有规则才能真正掌握它。在你花了一个人月调试某人“优化”的东西后,你会得到它。
编辑:
在这个帖子中发生了一件有趣的事情。检查#1 答案,这是编译器在优化事物方面的强大功能的一个示例。测试完全无效,因为可以完全排除非反射实例化。
上课?在编写干净、代码整洁的解决方案并证明它太慢之前,永远不要优化。
【讨论】:
您可能会发现 A a = new A() 正在被 JVM 优化。 如果将对象放入数组中,它们的性能就不会那么好。 ;) 以下打印...
new A(), 141 ns
A.class.newInstance(), 266 ns
new A(), 103 ns
A.class.newInstance(), 261 ns
public class Run {
private static final int RUNS = 3000000;
public static class A {
}
public static void main(String[] args) throws Exception {
doRegular();
doReflection();
doRegular();
doReflection();
}
public static void doRegular() throws Exception {
A[] as = new A[RUNS];
long start = System.nanoTime();
for (int i = 0; i < RUNS; i++) {
as[i] = new A();
}
System.out.printf("new A(), %,d ns%n", (System.nanoTime() - start)/RUNS);
}
public static void doReflection() throws Exception {
A[] as = new A[RUNS];
long start = System.nanoTime();
for (int i = 0; i < RUNS; i++) {
as[i] = A.class.newInstance();
}
System.out.printf("A.class.newInstance(), %,d ns%n", (System.nanoTime() - start)/RUNS);
}
}
这表明我的机器上的差异约为 150 ns。
【讨论】:
Class.getDeclaredMethod)然后多次调用Method.invoke? 我使用反射一次还是多次调用它? 后续问题,如果不是Method 而是Constructor 而我多次使用Constructor.newInstance 怎么办?
如果真的需要比反射更快的东西,而且这不仅仅是过早的优化,那么使用ASM 或更高级别的库生成字节码是一种选择。第一次生成字节码比只使用反射要慢,但是一旦生成字节码,它就和普通的 Java 代码一样快,并且会被 JIT 编译器优化。
使用代码生成的一些应用示例:
在 CGLIB 生成的代理上调用方法比 Java 的 dynamic proxies 稍快,因为 CGLIB 为其代理生成字节码,但动态代理仅使用反射(I measuredCGLIB 在方法上快 10 倍左右调用,但创建代理较慢)。
JSerial 生成用于读取/写入序列化对象字段的字节码,而不是使用反射。 JSerial 的网站上有some benchmarks。
我不是 100% 确定(而且我现在不想阅读源代码),但我认为 Guice 会生成字节码来进行依赖注入。如果我错了,请纠正我。
【讨论】:
“重要”完全取决于上下文。
如果您使用反射来基于某个配置文件创建单个处理程序对象,然后将其余时间花在运行数据库查询上,那么它就无关紧要了。如果您在紧密循环中通过反射创建大量对象,那么是的,这很重要。
一般来说,设计灵活性(如果需要!)应该推动您使用反射,而不是性能。但是,要确定性能是否是一个问题,您需要进行分析,而不是从讨论论坛中获得任意回复。
【讨论】:
反射有一些开销,但在现代虚拟机上它比以前小了很多。
如果您在程序中使用反射来创建每个简单对象,那么就会出现问题。偶尔使用它,当你有充分的理由时,完全不成问题。
【讨论】:
是的,使用反射时性能会受到影响,但可能的优化解决方法是缓存方法:
Method md = null; // Call while looking up the method at each iteration.
millis = System.currentTimeMillis( );
for (idx = 0; idx < CALL_AMOUNT; idx++) {
md = ri.getClass( ).getMethod("getValue", null);
md.invoke(ri, null);
}
System.out.println("Calling method " + CALL_AMOUNT+ " times reflexively with lookup took " + (System.currentTimeMillis( ) - millis) + " millis");
// Call using a cache of the method.
md = ri.getClass( ).getMethod("getValue", null);
millis = System.currentTimeMillis( );
for (idx = 0; idx < CALL_AMOUNT; idx++) {
md.invoke(ri, null);
}
System.out.println("Calling method " + CALL_AMOUNT + " times reflexively with cache took " + (System.currentTimeMillis( ) - millis) + " millis");
将导致:
[java] 使用查找自反调用方法 1000000 次耗时 5618 毫秒
[java] 使用缓存自反调用方法 1000000 次耗时 270 毫秒
【讨论】:
有趣的是,设置跳过安全检查的 setAccessible(true) 可以降低 20% 的成本。
没有 setAccessible(true)
new A(), 70 ns
A.class.newInstance(), 214 ns
new A(), 84 ns
A.class.newInstance(), 229 ns
使用 setAccessible(true)
new A(), 69 ns
A.class.newInstance(), 159 ns
new A(), 85 ns
A.class.newInstance(), 171 ns
【讨论】:
1000000 调用时,这些数字是否呈线性比例?
setAccessible()在一般情况下可以有更多的区别,特别是对于具有多个参数的方法,所以应该总是调用它。
反射很慢,尽管对象分配并不像反射的其他方面那样无望。使用基于反射的实例化实现同等性能需要您编写代码,以便 jit 可以判断正在实例化哪个类。如果无法确定类的身份,则无法内联分配代码。更糟糕的是,转义分析失败,并且对象无法堆栈分配。如果幸运的话,如果这段代码变热,JVM 的运行时分析可能会派上用场,并且可以动态确定哪个类占主导地位并可能针对该类进行优化。
请注意,此线程中的微基准测试存在严重缺陷,因此请谨慎对待。到目前为止,缺陷最少的是 Peter Lawrey 的:它会进行热身运行以使方法受到影响,并且它(有意识地)击败逃逸分析以确保分配实际发生。但是,即使这样也有问题:例如,可以预期大量的数组存储会破坏缓存和存储缓冲区,因此如果您的分配非常快,这将主要成为内存基准。 (感谢彼得得出正确的结论:差异是“150ns”而不是“2.5x”。我怀疑他做这种事情是为了谋生。)
【讨论】:
是的,它明显变慢了。我们正在运行一些执行此操作的代码,虽然我目前没有可用的指标,但最终结果是我们不得不重构该代码以不使用反射。如果你知道类是什么,直接调用构造函数就行了。
【讨论】:
在 doReflection() 中是开销,因为 Class.forName("misc.A") (这将需要类查找,可能会扫描 filsystem 上的类路径),而不是调用 newInstance()班级。我想知道如果 Class.forName("misc.A") 只在 for 循环之外执行一次,统计信息会是什么样子,实际上不必为循环的每次调用都执行它。
【讨论】:
是的,通过反射创建对象总是会变慢,因为 JVM 无法在编译时优化代码。有关详细信息,请参阅 Sun/Java Reflection tutorials。
看这个简单的测试:
public class TestSpeed {
public static void main(String[] args) {
long startTime = System.nanoTime();
Object instance = new TestSpeed();
long endTime = System.nanoTime();
System.out.println(endTime - startTime + "ns");
startTime = System.nanoTime();
try {
Object reflectionInstance = Class.forName("TestSpeed").newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
endTime = System.nanoTime();
System.out.println(endTime - startTime + "ns");
}
}
【讨论】:
Class.forName()) 与实例化 (newInstance()) 分开,因为它们的性能特征差异很大,您偶尔可以避免在设计良好的情况下重复查找系统。
您通常可以使用 Apache commons BeanUtils 或 PropertyUtils 来进行自省(基本上它们会缓存有关类的元数据,因此它们并不总是需要使用反射)。
【讨论】:
我认为这取决于目标方法的轻/重。如果目标方法很轻(例如 getter/setter),它可能会慢 1 ~ 3 倍。如果目标方法需要大约 1 毫秒或以上,那么性能将非常接近。这是我使用 Java 8 和 reflectasm 进行的测试:
public class ReflectionTest extends TestCase {
@Test
public void test_perf() {
Profiler.run(3, 100000, 3, "m_01 by refelct", () -> Reflection.on(X.class)._new().invoke("m_01")).printResult();
Profiler.run(3, 100000, 3, "m_01 direct call", () -> new X().m_01()).printResult();
Profiler.run(3, 100000, 3, "m_02 by refelct", () -> Reflection.on(X.class)._new().invoke("m_02")).printResult();
Profiler.run(3, 100000, 3, "m_02 direct call", () -> new X().m_02()).printResult();
Profiler.run(3, 100000, 3, "m_11 by refelct", () -> Reflection.on(X.class)._new().invoke("m_11")).printResult();
Profiler.run(3, 100000, 3, "m_11 direct call", () -> X.m_11()).printResult();
Profiler.run(3, 100000, 3, "m_12 by refelct", () -> Reflection.on(X.class)._new().invoke("m_12")).printResult();
Profiler.run(3, 100000, 3, "m_12 direct call", () -> X.m_12()).printResult();
}
public static class X {
public long m_01() {
return m_11();
}
public long m_02() {
return m_12();
}
public static long m_11() {
long sum = IntStream.range(0, 10).sum();
assertEquals(45, sum);
return sum;
}
public static long m_12() {
long sum = IntStream.range(0, 10000).sum();
assertEquals(49995000, sum);
return sum;
}
}
}
完整的测试代码在 GitHub:ReflectionTest.java
【讨论】: