클래스 생성자를 호출하는 대신 리플렉션을 사용하여 객체를 생성하면 성능에 큰 차이가 있습니까?
답변
네 그럼요. 반사를 통해 클래스를 찾는 것은 규모가 비싸다.
리플렉션에 대한 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라는 것을 나타냅니다.
답변
이 경우 정말 뭔가 필요가 빠른 반사보다, 그것은 단지 조기 최적화, 다음으로 바이트 코드 생성이 아니다 ASM 또는 높은 수준의 라이브러리 옵션입니다. 처음으로 바이트 코드를 생성하는 것은 리플렉션을 사용하는 것보다 느리지 만 일단 바이트 코드가 생성되면 일반적인 Java 코드만큼 빠르며 JIT 컴파일러에 의해 최적화됩니다.
코드 생성을 사용하는 응용 프로그램의 예 :
-
에 의해 생성 된 프록시에 메소드 호출 CGLIB 것은 약간 빠른 자바보다 동적 프록시 CGLIB는 프록시에 대한 바이트 코드를 생성하기 때문에,하지만, 동적 프록시는 반사를 (사용 I 측정 빠른 메소드 호출의 10 배에 대해 할 CGLIB를하지만, 만드는 프록시가 느렸다).
-
JSerial 은 리플렉션을 사용하는 대신 직렬화 된 객체의 필드를 읽거나 쓰는 바이트 코드를 생성합니다. JSerial 사이트 에는 몇 가지 벤치 마크 가 있습니다.
-
100 % 확실하지는 않지만 (지금 소스를 읽는 느낌이 들지 않습니다) Guice 는 의존성 주입을 수행하기 위해 바이트 코드를 생성 한다고 생각 합니다. 틀 렸으면 말해줘.
답변
“중요한”은 전적으로 맥락에 의존합니다.
리플렉션을 사용하여 일부 구성 파일을 기반으로 단일 처리기 객체를 만든 다음 데이터베이스 쿼리를 실행하는 데 나머지 시간을 소비하는 경우 중요하지 않습니다. 타이트한 루프에서 반사를 통해 많은 수의 객체를 생성하는 경우 중요합니다.
일반적으로 디자인 유연성 (필요한 경우!)은 성능이 아닌 반사를 사용하도록합니다. 그러나 성능이 문제인지 여부를 판별하려면 토론 포럼에서 임의의 응답을 얻는 것이 아니라 프로파일 링해야합니다.
답변
리플렉션에는 약간의 오버 헤드가 있지만 현대 VM에서는 예전보다 훨씬 작습니다.
리플렉션을 사용하여 프로그램에서 모든 간단한 객체를 만드는 경우 무언가 잘못되었습니다. 정당한 이유가있을 때 가끔 사용하면 전혀 문제가되지 않습니다.
답변
예, 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 밀리 초 걸렸다