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

최근에 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 + "";

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



답변

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

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


답변

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

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

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

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

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


답변