65 개 요소로 구성된 배열을 선언하는 것보다 1000 배 빠른 64 개 요소로 여러 배열을 선언합니다.


91

최근에 64 개 요소를 포함하는 배열을 선언하는 것이 65 개 요소로 동일한 유형의 배열을 선언하는 것보다 훨씬 빠릅니다 (> 1000 배).

이것을 테스트하는 데 사용한 코드는 다음과 같습니다.

public class Tests{
    public static void main(String args[]){
        double start = System.nanoTime();
        int job = 100000000;//100 million
        for(int i = 0; i < job; i++){
            double[] test = new double[64];
        }
        double end = System.nanoTime();
        System.out.println("Total runtime = " + (end-start)/1000000 + " ms");
    }
}

이 작업은 약 6ms에 실행되며 교체 new double[64]하는 new double[65]경우 약 7 초가 걸립니다. 이 문제는 작업이 점점 더 많은 스레드에 분산되면 기하 급수적으로 더 심각해집니다.

이 문제는 또한 다음과 같은 배열의 다른 유형의 발생 int[65]또는 String[65]. 이 문제는 큰 문자열 String test = "many characters";에서는 발생하지 않지만 다음으로 변경되면 발생하기 시작합니다.String test = i + "";

왜 이것이 사실인지 그리고이 문제를 피할 수 있는지 궁금합니다.


3
참고 : 벤치마킹 System.nanoTime()보다 선호되어야합니다 System.currentTimeMillis().
rocketboy

4
난 그냥 궁금해? Linux를 사용하고 계십니까? OS에 따라 동작이 바뀌나요?
bsd

9
도대체 어떻게이 질문이 반대표를 얻었습니까 ??
Rohit Jain

2
FWIW byte대신 이 코드를 실행하면 유사한 성능 불일치가 나타납니다 double.
Oliver Charlesworth 2013-09-15

3
@ThomasJungblut : 그렇다면 OP의 실험에서 불일치를 설명하는 것은 무엇입니까?
Oliver Charlesworth 2013-09-15

답변:


88

Java VM의 JIT 컴파일러가 수행 한 최적화 로 인해 발생하는 동작을 관찰하고 있습니다 . 이 동작은 최대 64 개 요소의 스칼라 배열로 트리거되어 재현 가능하며 64보다 큰 배열에서는 트리거되지 않습니다.

자세히 살펴보기 전에 루프 본문을 자세히 살펴 보겠습니다.

double[] test = new double[64];

신체는 효과가 없습니다 (관찰 가능한 행동) . 즉,이 명령문의 실행 여부에 관계없이 프로그램 실행 외부에서 아무런 차이가 없습니다. 전체 루프에 대해서도 마찬가지입니다. 따라서 코드 옵티마이 저가 루프를 동일한 기능과 다른 타이밍 동작을 가진 어떤 것으로 (또는 아무것도) 변환 하지 않을 수 있습니다.

벤치 마크의 경우 최소한 다음 두 가지 지침을 준수해야합니다. 그렇게했다면 그 차이는 훨씬 더 작았을 것입니다.

  • 벤치 마크를 여러 번 실행하여 JIT 컴파일러 (및 최적화 프로그램)를 워밍업합니다.
  • 모든 표현의 결과를 사용하고 벤치 마크 끝에 인쇄하십시오.

이제 자세히 살펴 보겠습니다. 당연히 64 요소보다 크지 않은 스칼라 배열에 대해 트리거되는 최적화가 있습니다. 최적화는 탈출 분석의 일부입니다 . 작은 개체와 작은 배열을 힙에 할당하는 대신 스택에 배치하거나 완전히 최적화하는 것이 좋습니다. 2005 년에 작성된 Brian Goetz의 다음 기사에서 이에 대한 정보를 찾을 수 있습니다.

명령 줄 옵션을 사용하여 최적화를 비활성화 할 수 있습니다 -XX:-DoEscapeAnalysis. 스칼라 배열의 매직 값 64는 명령 줄에서도 변경할 수 있습니다. 다음과 같이 프로그램을 실행하면 64 개와 65 개 요소가있는 배열간에 차이가 없습니다.

java -XX:EliminateAllocationArraySizeLimit=65 Tests

하지만 이러한 명령 줄 옵션을 사용하지 않는 것이 좋습니다. 나는 그것이 현실적인 응용 프로그램에 큰 차이를 만들지 의심합니다. 필자는 일부 의사 벤치 마크의 결과를 기반으로하지 않고 필요성을 절대적으로 확신 할 경우에만 사용합니다.


9
그러나 옵티마이
저가

10
@nosid : OP의 코드는 현실적이지 않을 수 있지만 JVM에서 흥미 롭거나 예상치 못한 동작을 분명히 유발하여 다른 상황에 영향을 미칠 수 있습니다. 왜 이런 일이 일어나는지 묻는 것이 타당하다고 생각합니다.
Oliver Charlesworth 2013-09-15

1
@ThomasJungblut 루프가 제거되었다고 생각하지 않습니다. 루프 외부에 "int total"을 추가하고 "total + = test [0];"을 추가 할 수 있습니다. 위의 예에. 그런 다음 결과를 인쇄하면 총액이 1 억이라는 것을 알 수 있으며 1 초도 안되는 시간에 스텁이 실행됩니다.
Sipko 2013 년

1
온 스택 교체는 힙 할당을 스택 할당으로 바꾸는 대신 해석 된 코드를 즉시 컴파일 된 코드로 바꾸는 것입니다. EliminateAllocationArraySizeLimit는 이스케이프 분석에서 스칼라 교체 가능한 것으로 간주되는 배열의 제한 크기입니다. 따라서 효과가 컴파일러 최적화에 기인한다는 요점은 정확하지만 스택 할당으로 인한 것이 아니라 이스케이프 분석 단계로 인해 할당이 필요하지 않습니다.
kiheru

2
@Sipko : 애플리케이션이 스레드 수에 따라 확장되지 않는다고 작성하고 있습니다. 이는 문제가 귀하가 요청하는 마이크로 최적화와 관련이 없다는 표시입니다. 작은 부분보다는 큰 그림을 보는 것이 좋습니다.
nosid

2

개체의 크기에 따라 차이가있을 수있는 방법에는 여러 가지가 있습니다.

nosid가 언급했듯이 JITC는 스택에 작은 "로컬"개체를 할당 할 수 있으며 "작은"배열의 크기 컷오프는 64 요소 일 수 있습니다.

스택에 할당하는 것이 힙에 할당하는 것보다 훨씬 빠르며, 더 나아가 스택은 가비지 수집이 필요하지 않으므로 GC 오버 헤드가 크게 감소합니다. (이 테스트 케이스의 경우 GC 오버 헤드는 총 실행 시간의 80-90 % 일 가능성이 높습니다.)

또한 값이 스택 할당되면 JITC는 "데드 코드 제거"를 수행하고 그 결과가 new어디에도 사용되지 않는지 확인하고 손실 될 부작용이 없는지 확인한 후 전체 new작업을 제거 할 수 있습니다 . 그런 다음 (현재 비어있는) 루프 자체가 있습니다.

JITC가 스택 할당을 수행하지 않더라도 특정 크기보다 작은 객체가 더 큰 객체와 다르게 (예 : 다른 "공간"에서) 힙에 할당되는 것이 전적으로 가능합니다. (일반적으로 이것은 그렇게 극적인 타이밍 차이를 생성하지 않습니다.)


이 스레드에 늦었습니다. 힙에 할당하는 것보다 스택에 할당하는 것이 더 빠른 이유는 무엇입니까? 몇몇 기사에 따르면 힙에 할당하는 데는 ~ 12 개의 명령어가 필요합니다. 개선의 여지가 많지 않습니다.
Vortex

@Vortex-스택에 할당하려면 1-2 개의 명령어가 필요합니다. 그러나 그것은 전체 스택 프레임을 할당하는 것입니다. 루틴을위한 레지스터 저장 영역을 가지려면 스택 프레임을 할당해야하므로 동시에 할당 된 다른 변수는 "자유"입니다. 그리고 내가 말했듯이 스택에는 GC가 필요하지 않습니다. 힙 항목에 대한 GC 오버 헤드는 힙 할당 작업의 비용보다 훨씬 큽니다.
Hot Licks
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.