답변:
네 그럼요. 반사를 통해 클래스를 찾는 것은 규모가 비싸다.
리플렉션에 대한 Java 문서 인용 :
리플렉션에는 동적으로 분석되는 유형이 포함되므로 특정 Java 가상 머신 최적화를 수행 할 수 없습니다. 결과적으로, 반사 작업은 비 반사 작업보다 성능이 느리므로 성능에 민감한 응용 프로그램에서 자주 호출되는 코드 섹션에서는 피해야합니다.
다음은 Sun JRE 6u10을 실행하는 내 컴퓨터에서 5 분 안에 해킹 한 간단한 테스트입니다.
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.
예, 느립니다.
그러나 빌어 먹을 # 1 규칙을 기억하십시오-프리 마이징 최적화는 모든 사악의 뿌리입니다
(글쎄, DRY의 경우 # 1과 연결될 수 있음)
맹세합니다. 누군가가 직장에서 나에게 와서 나에게 물었다면 앞으로 몇 달 동안 코드를 잘 살펴볼 것입니다.
필요할 때까지 최적화하지 말고 읽을 수있는 코드를 작성하십시오.
아, 그리고 바보 같은 코드를 쓰는 것도 아닙니다. 복사 및 붙여 넣기 등의 작업을 수행 할 수있는 가장 깨끗한 방법에 대해 생각하십시오. "나쁜"프로그래밍)
이런 질문이 들리면 정말 놀랍지 만 모든 규칙을 실제로 배우기 전에 스스로 배우는 것을 잊어 버립니다. 누군가 "최적화 된"무언가를 디버깅하는 데 한 달에 한 번 보낸 후에 얻을 수 있습니다.
편집하다:
이 글에서 흥미로운 일이 일어났습니다. # 1 답변을 확인하십시오. 컴파일러가 최적화하는 데 얼마나 강력한 지 보여주는 예입니다. 비 반사 인스턴스화를 완전히 제거 할 수 있으므로 테스트가 완전히 유효하지 않습니다.
교훈? 깨끗하고 깔끔하게 코딩 된 솔루션을 작성하고 너무 느리다는 것이 입증 될 때까지 절대 최적화하지 마십시오.
JVM에서 A a = new A ()가 최적화되고 있음을 알 수 있습니다. 객체를 배열에 넣으면 성능이 좋지 않습니다. ;) 다음 인쇄
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);
}
}
이것은 내 컴퓨터의 차이가 약 150ns라는 것을 나타냅니다.
Class.getDeclaredMethod
) 한 다음 Method.invoke
여러 번 호출 하면? 반사를 한 번 또는 여러 번 사용하여 호출합니까? 대신의 경우 질문을 따라, 어떤 Method
그것입니다 Constructor
내가 할 Constructor.newInstance
여러 번?
이 경우 정말 뭔가 필요가 빠른 반사보다, 그것은 단지 조기 최적화, 다음으로 바이트 코드 생성이 아니다 ASM 또는 높은 수준의 라이브러리 옵션입니다. 처음으로 바이트 코드를 생성하는 것은 리플렉션을 사용하는 것보다 느리지 만 일단 바이트 코드가 생성되면 일반적인 Java 코드만큼 빠르며 JIT 컴파일러에 의해 최적화됩니다.
코드 생성을 사용하는 응용 프로그램의 예 :
에 의해 생성 된 프록시에 메소드 호출 CGLIB 것은 약간 빠른 자바보다 동적 프록시 CGLIB는 프록시에 대한 바이트 코드를 생성하기 때문에,하지만, 동적 프록시는 반사를 (사용 I 측정 빠른 메소드 호출의 10 배에 대해 할 CGLIB를하지만, 만드는 프록시가 느렸다).
JSerial 은 리플렉션을 사용하는 대신 직렬화 된 객체의 필드를 읽거나 쓰는 바이트 코드를 생성합니다. JSerial 사이트 에는 몇 가지 벤치 마크 가 있습니다.
100 % 확실하지는 않지만 (지금 소스를 읽는 느낌이 들지 않습니다) Guice 는 의존성 주입을 수행하기 위해 바이트 코드를 생성 한다고 생각 합니다. 틀 렸으면 말해줘.
예, Reflection을 사용할 때 성능이 저하되지만 최적화를위한 가능한 해결 방법은 메서드를 캐싱하는 것입니다.
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");
결과 :
[자바] 조회와 함께 1000000 번을 회귀 적으로 호출하는 방법은 5618 밀리
[자바] 캐시로 1000000 번을 회귀 적으로 호출하는 방법은 270 밀리 초 걸렸다
객체 할당이 다른 반사 측면만큼 절망적이지는 않지만 반사가 느립니다. 리플렉션 기반 인스턴스화로 동등한 성능을 달성하려면 코드를 작성해야 jit이 인스턴스화중인 클래스를 알 수 있습니다. 클래스의 신원을 확인할 수 없으면 할당 코드를 인라인 할 수 없습니다. 더군다나, 이스케이프 분석이 실패하고 객체를 스택 할당 할 수 없습니다. 운이 좋으면이 코드가 뜨거워지면 JVM의 런타임 프로파일 링이 구출 될 수 있으며 어떤 클래스가 지배적이며 해당 클래스에 대해 최적화 할 수 있는지 동적으로 결정할 수 있습니다.
이 스레드의 마이크로 벤치 마크는 깊은 결함이 있으므로 소금 알갱이로 가져 가십시오. 피터로 레이 (Peter Lawrey)는 결함이 가장 적습니다. 워밍업은 방법을 익히기 위해 실행되며, (의도적으로) 이스케이프 분석을 무시하여 실제로 할당이 이루어 지도록합니다. 그러나 그 중 하나에도 문제가 있습니다. 예를 들어, 엄청난 수의 어레이 저장소는 캐시와 저장소 버퍼를 물리 칠 것으로 예상되므로 할당이 매우 빠르면 대부분 메모리 벤치 마크가 될 것입니다. (그러나 결론을 얻는 것에 대한 Peter의 말 : 그 차이는 "2.5x"가 아니라 "150ns"입니다. 나는 그가 이런 종류의 일을 생계를 위해하는 것 같습니다.)
흥미롭게도 보안 검사를 건너 뛰는 setAccessible (true) settting은 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()
여러 인수를 가진 메소드의 경우 일반적으로 훨씬 더 큰 차이가있을 수 있으므로 항상 호출해야합니다.
예, 상당히 느립니다. 우리는 그렇게하는 코드를 실행 중이며 현재 사용 가능한 메트릭스가 없지만 최종 결과는 리플렉션을 사용하지 않도록 해당 코드를 리팩터링해야한다는 것입니다. 클래스가 무엇인지 아는 경우 생성자를 직접 호출하십시오.
예, JVM이 컴파일 타임에 코드를 최적화 할 수 없기 때문에 항상 리플렉션에 의해 객체를 생성하는 속도가 느려집니다. 자세한 내용은 Sun / Java Reflection 자습서 를 참조하십시오.
이 간단한 테스트를보십시오 :
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 ())와 분리해야합니다. 성능 특성이 크게 다르고 때때로 잘 설계된 시스템에서 반복 조회를 피할 수 있기 때문입니다.
나는 그것이 목표 방법이 얼마나 가볍고 무거운 지에 달려 있다고 생각합니다. 대상 방법이 매우 가벼우면 (예 : 게터 / 세터) 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