자바 리플렉션 성능


답변:


169

네 그럼요. 반사를 통해 클래스를 찾는 것은 규모가 비싸다.

리플렉션에 대한 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.


5
내 컴퓨터에서 하나의 Class.forName () 호출만으로 .newInstance () 호출의 점수는 30 정도입니다. VM 버전에 따라 적절한 캐싱 전략으로 생각하는 것보다 차이가 더 클 수 있습니다.
Sean Reilly

56
아래의 @Peter Lawrey는 컴파일러가 비 반사 솔루션을 최적화했기 때문에이 테스트가 완전히 유효하지 않다고 지적했습니다 (아무 것도 수행하지 않았 음을 확인하고 for 루프를 최적화 할 수 있음). 재 작업이 필요하며 잘못된 / 오도 정보로서 SO에서 제거해야합니다. 최적화 프로그램이 최적화하지 못하도록 두 경우 모두 생성 된 객체를 어레이에 캐시합니다. (생성자가 부작용을 가지고 있지 않다는 것을 증명할 수 없기 때문에 반사적 인 상황에서는 이것을 할 수 없습니다)
Bill K

6
@ 빌 K-멀리 가지 말자. 예, 최적화로 인해 숫자가 꺼져 있습니다. 아니요, 테스트가 완전히 유효 하지는 않습니다. 결과가 왜곡 될 가능성을 제거하는 호출을 추가했으며 여전히 반사에 대해 숫자가 누적됩니다. 어쨌든 이것은 매우 거친 마이크로 벤치 마크라는 것을 기억하십시오. 이것은 단지 반사가 항상 특정한 오버 헤드를
유발

4
이것은 아마도 쓸모없는 벤치 마크 일 것입니다. doSomething의 기능에 따라 다릅니다. 부작용이 눈에 띄지 않으면 벤치 마크는 죽은 코드 만 실행합니다.
nes1983

9
방금 JVM 최적화 리플렉션 35 배를 목격했습니다. 루프에서 반복적으로 테스트를 실행하면 최적화 된 코드를 테스트하는 방법입니다. 첫 번째 반복 : 3045ms, 두 번째 반복 : 2941ms, 세 번째 반복 : 90ms, 네 번째 반복 : 83ms. 코드 : c.newInstance (i). c는 생성자입니다. 비 반사 코드 : 새로운 A (i). 13, 4, 3.ms 시간. 그렇습니다.이 경우에는 반사가 느리지 만 사람들이 결론 짓는 것만 큼 느리지는 않았습니다. 내가보고있는 모든 테스트에서 JVM에 바이트 코드를 기계로 대체 할 수있는 기회를주지 않고 테스트를 한 번만 실행하기 때문입니다. 암호.
Mike

87

예, 느립니다.

그러나 빌어 먹을 # 1 규칙을 기억하십시오-프리 마이징 최적화는 모든 사악의 뿌리입니다

(글쎄, DRY의 경우 # 1과 연결될 수 있음)

맹세합니다. 누군가가 직장에서 나에게 와서 나에게 물었다면 앞으로 몇 달 동안 코드를 잘 살펴볼 것입니다.

필요할 때까지 최적화하지 말고 읽을 수있는 코드를 작성하십시오.

아, 그리고 바보 같은 코드를 쓰는 것도 아닙니다. 복사 및 붙여 넣기 등의 작업을 수행 할 수있는 가장 깨끗한 방법에 대해 생각하십시오. "나쁜"프로그래밍)

이런 질문이 들리면 정말 놀랍지 만 모든 규칙을 실제로 배우기 전에 스스로 배우는 것을 잊어 버립니다. 누군가 "최적화 된"무언가를 디버깅하는 데 한 달에 한 번 보낸 후에 얻을 수 있습니다.

편집하다:

이 글에서 흥미로운 일이 일어났습니다. # 1 답변을 확인하십시오. 컴파일러가 최적화하는 데 얼마나 강력한 지 보여주는 예입니다. 비 반사 인스턴스화를 완전히 제거 할 수 있으므로 테스트가 완전히 유효하지 않습니다.

교훈? 깨끗하고 깔끔하게 코딩 된 솔루션을 작성하고 너무 느리다는 것이 입증 될 때까지 절대 최적화하지 마십시오.


28
이 답변에 대한 정서에 전적으로 동의하지만, 주요 디자인 결정을 시작하려는 경우 성능에 대한 아이디어를 얻는 데 도움이되므로 완전히 작동 할 수없는 길을 떠나지 않아야합니다. 어쩌면 그는 단지 실사를하고 있습니까?
Limbic System

26
-1 : 잘못된 길을 피하는 것은 최적화가 아니라 단지 일을하는 것입니다. 최적화는 실제 또는 가상의 성능 문제로 인해 잘못되고 복잡한 방식으로 수행됩니다.
soru

5
@soru는 전적으로 동의합니다. 삽입 정렬을 위해 배열 목록에서 링크 된 목록을 선택하는 것이 올바른 방법입니다. 그러나이 특정 질문은 원래 질문의 양쪽에 좋은 사용 사례가 있으므로 가장 유용한 솔루션이 아닌 성능에 따라 하나를 선택하는 것이 잘못 될 수 있습니다. 나는 우리가 전혀 동의하지 않는다고 확신하지 못하므로 왜 "-1"이라고 말했는지 잘 모르겠습니다.
Bill K

14
현명한 분석가 프로그래머는 초기 단계에서 효율성을 고려해야합니다. 그렇지 않으면 효율적이고 비용이 많이 드는 시간대에 최적화 할 수없는 시스템이 생길 수 있습니다. 아니요, 모든 클럭 사이클을 최적화하지는 않지만 클래스 인스턴스화와 같은 기본 사항에 대해 모범 사례를 사용하는 것이 가장 확실합니다. 이 예는 당신이 성찰에 관해 그러한 질문들을 고려하는 이유 중 하나입니다. 백만 라인 시스템을 통해 반사를 사용하여 나중에 수십 배가 너무 느리다는 것을 발견 한 것은 꽤 가난한 프로그래머 일 것입니다.
RichieHH

2
@Richard Riley 일반적으로 클래스 인스턴스화는 선택한 클래스의 리플렉션을 사용하는 매우 드문 이벤트입니다. 그래도 당신이 옳다고 생각합니다. 어떤 사람들은 모든 클래스를 반영하여 인스턴스화 할 수도 있습니다. 나는 꽤 나쁜 프로그래밍이라고 부릅니다 (그러나 그럼에도 불구하고 당신은 사실 이후 재사용을 위해 클래스 인스턴스 캐시를 구현할 수 있고 코드에 너무 많은 해를 끼치 지 않아야합니다. 그래서 나는 항상 가독성을 위해 디자인하고 프로파일 링하고 최적화한다고 말할 것입니다 이상)
빌 K

36

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라는 것을 나타냅니다.


최적화 프로그램을 방금 종료 했으므로 이제 두 버전이 느립니다. 따라서 반사는 여전히 느립니다.
gbjbaanb

13
@gbjbaanb 옵티마이 저가 생성 자체를 최적화하고 있다면 유효한 테스트가 아닙니다. 따라서 @Peter의 테스트는 실제로 생성 시간을 비교하기 때문에 유효합니다 (실제 상황에서는 인스턴스화하는 객체가 필요하기 때문에 옵티마이 저는 실제 상황에서는 작동하지 않습니다).
Bill K

10
@ nes1983이 경우 더 나은 벤치 마크를 만들 수 있습니다. 아마도 메소드 본문에 있어야하는 것과 같이 건설적인 것을 제공 할 수 있습니다.
Peter Lawrey

1
내 맥, openjdk 7u4에서 차이는 95ns와 100ns입니다. 배열에 A를 저장하는 대신 hashCode를 저장합니다. -verbose : class라고하면 핫스팟이 A를 구성하기위한 바이트 코드를 생성하는시기와 그에 따른 속도 향상을 볼 수 있습니다.
Ron

@PeterLawrey 한 번 검색 (한 번의 호출 Class.getDeclaredMethod) 한 다음 Method.invoke여러 번 호출 하면? 반사를 한 번 또는 여러 번 사용하여 호출합니까? 대신의 경우 질문을 따라, 어떤 Method그것입니다 Constructor내가 할 Constructor.newInstance여러 번?
tmj

28

이 경우 정말 뭔가 필요가 빠른 반사보다, 그것은 단지 조기 최적화, 다음으로 바이트 코드 생성이 아니다 ASM 또는 높은 수준의 라이브러리 옵션입니다. 처음으로 바이트 코드를 생성하는 것은 리플렉션을 사용하는 것보다 느리지 만 일단 바이트 코드가 생성되면 일반적인 Java 코드만큼 빠르며 JIT 컴파일러에 의해 최적화됩니다.

코드 생성을 사용하는 응용 프로그램의 예 :

  • 에 의해 생성 된 프록시에 메소드 호출 CGLIB 것은 약간 빠른 자바보다 동적 프록시 CGLIB는 프록시에 대한 바이트 코드를 생성하기 때문에,하지만, 동적 프록시는 반사를 (사용 I 측정 빠른 메소드 호출의 10 배에 대해 할 CGLIB를하지만, 만드는 프록시가 느렸다).

  • JSerial 은 리플렉션을 사용하는 대신 직렬화 된 객체의 필드를 읽거나 쓰는 바이트 코드를 생성합니다. JSerial 사이트 에는 몇 가지 벤치 마크 가 있습니다.

  • 100 % 확실하지는 않지만 (지금 소스를 읽는 느낌이 들지 않습니다) Guice 는 의존성 주입을 수행하기 위해 바이트 코드를 생성 한다고 생각 합니다. 틀 렸으면 말해줘.


27

"중요한"은 전적으로 맥락에 의존합니다.

리플렉션을 사용하여 일부 구성 파일을 기반으로 단일 처리기 객체를 만든 다음 데이터베이스 쿼리를 실행하는 데 나머지 시간을 소비하는 경우 중요하지 않습니다. 타이트한 루프에서 반사를 통해 많은 수의 객체를 생성하는 경우 중요합니다.

일반적으로 디자인 유연성 (필요한 경우!)은 성능이 아닌 반사를 사용하도록합니다. 그러나 성능이 문제인지 여부를 판별하려면 토론 포럼에서 임의의 응답을 얻는 것이 아니라 프로파일 링해야합니다.


24

리플렉션에는 약간의 오버 헤드가 있지만 현대 VM에서는 예전보다 훨씬 작습니다.

리플렉션을 사용하여 프로그램에서 모든 간단한 객체를 만드는 경우 무언가 잘못되었습니다. 정당한 이유가있을 때 가끔 사용하면 전혀 문제가되지 않습니다.


11

예, 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 밀리 초 걸렸다


메소드 / 생성자를 재사용하는 것이 실제로 유용하고 도움이되지만 위의 테스트는 일반적인 벤치마킹 문제로 인해 의미있는 숫자를 나타내지 않습니다 (예열이 없으므로 첫 번째 루프는 특히 JVM / JIT 예열 시간을 측정합니다).
StaxMan

7

객체 할당이 다른 반사 측면만큼 절망적이지는 않지만 반사가 느립니다. 리플렉션 기반 인스턴스화로 동등한 성능을 달성하려면 코드를 작성해야 jit이 인스턴스화중인 클래스를 알 수 있습니다. 클래스의 신원을 확인할 수 없으면 할당 코드를 인라인 할 수 없습니다. 더군다나, 이스케이프 분석이 실패하고 객체를 스택 할당 할 수 없습니다. 운이 좋으면이 코드가 뜨거워지면 JVM의 런타임 프로파일 링이 구출 될 수 있으며 어떤 클래스가 지배적이며 해당 클래스에 대해 최적화 할 수 있는지 동적으로 결정할 수 있습니다.

이 스레드의 마이크로 벤치 마크는 깊은 결함이 있으므로 소금 알갱이로 가져 가십시오. 피터로 레이 (Peter Lawrey)는 결함이 가장 적습니다. 워밍업은 방법을 익히기 위해 실행되며, (의도적으로) 이스케이프 분석을 무시하여 실제로 할당이 이루어 지도록합니다. 그러나 그 중 하나에도 문제가 있습니다. 예를 들어, 엄청난 수의 어레이 저장소는 캐시와 저장소 버퍼를 물리 칠 것으로 예상되므로 할당이 매우 빠르면 대부분 메모리 벤치 마크가 될 것입니다. (그러나 결론을 얻는 것에 대한 Peter의 말 : 그 차이는 "2.5x"가 아니라 "150ns"입니다. 나는 그가 이런 종류의 일을 생계를 위해하는 것 같습니다.)


7

흥미롭게도 보안 검사를 건너 뛰는 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

1
원칙적으로 나에게 명백한 것 같습니다. 1000000호출을 실행할 때이 숫자가 선형으로 확장 됩니까?
Lukas Eder

실제로 setAccessible()여러 인수를 가진 메소드의 경우 일반적으로 훨씬 더 큰 차이가있을 수 있으므로 항상 호출해야합니다.
StaxMan

6

예, 상당히 느립니다. 우리는 그렇게하는 코드를 실행 중이며 현재 사용 가능한 메트릭스가 없지만 최종 결과는 리플렉션을 사용하지 않도록 해당 코드를 리팩터링해야한다는 것입니다. 클래스가 무엇인지 아는 경우 생성자를 직접 호출하십시오.


1
+1 비슷한 경험을했습니다. 반드시 필요한 경우에만 반사를 사용하는 것이 좋습니다.
Ryan Thames

예를 들어 AOP 기반 라이브러리는 반영이 필요합니다.
gaurav

4

doReflection ()에는 클래스에서 호출 된 newInstance ()가 아니라 Class.forName ( "misc.A") (파일 시스템에서 클래스 경로를 스캔하는 클래스 조회가 필요함)로 인한 오버 헤드가 있습니다. Class.forName ( "misc.A")이 for-loop 외부에서 한 번만 수행되면 통계가 어떻게 표시되는지 궁금합니다. 루프를 호출 할 때마다 수행 할 필요는 없습니다.


1

예, 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");
    }
}

3
조회 ( Class.forName())를 인스 턴시 (newInstance ())와 분리해야합니다. 성능 특성이 크게 다르고 때때로 잘 설계된 시스템에서 반복 조회를 피할 수 있기 때문입니다.
Joachim Sauer

3
또한 유용한 벤치 마크를 얻으려면 각 작업을 여러 번 여러 번 실행해야합니다. 먼저 모든 작업이 너무 느리게 측정되어 안정적으로 측정되지 않으며, 둘째로 유용한 숫자를 얻으려면 HotSpot VM을 준비해야합니다.
Joachim Sauer

1

종종 조사하는 Apache commons BeanUtils 또는 PropertyUtils를 사용할 수 있습니다 (기본적으로 클래스에 대한 메타 데이터를 캐시하므로 항상 리플렉션을 사용할 필요는 없습니다).


0

나는 그것이 목표 방법이 얼마나 가볍고 무거운 지에 달려 있다고 생각합니다. 대상 방법이 매우 가벼우면 (예 : 게터 / 세터) 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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.