[java] Java에서는 int 대신 byte 또는 short를 사용하고 double 대신 float를 사용하는 것이 더 효율적입니까?

나는 숫자가 아무리 작거나 커야 할지라도 항상 int와 doubles를 사용한다는 것을 알았습니다. 따라서 Java에서는 byte또는 short대신 intfloat대신 사용하는 것이 더 효율적 double입니까?

따라서 int와 double이 많은 프로그램이 있다고 가정하십시오. 숫자가 적합하다는 것을 알고 있다면 int를 바이트 또는 반바지로 변경하고 변경할 가치가 있습니까?

나는 자바에 서명되지 않은 유형이 없다는 것을 알고 있지만 숫자가 양수라는 것을 알았다면 할 수있는 추가 사항이 있습니까?

효율성이란 대부분 처리를 의미합니다. 모든 변수의 크기가 절반이고 계산도 다소 빠르면 가비지 수집기가 훨씬 빠르다고 가정합니다. (나는 안드로이드에서 일하고 있기 때문에 램에 대해서도 다소 걱정할 필요가 있다고 생각합니다)

(가비지 수집기는 원시가 아닌 객체 만 처리한다고 가정하지만 여전히 버려진 객체의 모든 원시를 삭제합니까?)

내가 가지고있는 작은 안드로이드 앱으로 시도했지만 전혀 차이를 느끼지 못했습니다. (나는 아무것도 “과학적으로”측정하지 않았지만.)

더 빠르고 효율적이어야한다고 생각하는 것이 잘못입니까? 내가 시간을 낭비했음을 알기 위해 대규모 프로그램에서 모든 것을 변경하고 싶지 않습니다.

새 프로젝트를 시작할 때 처음부터 할 가치가 있습니까? (내 말은 조금이라도 도움이 될 것이라고 생각하지만, 그렇다면 다시 한 번, 왜 누군가가 그렇게하는 것처럼 보이지 않는 것 같습니다.)



답변

더 빠르고 효율적이어야한다고 생각하는 것이 잘못입니까? 내가 시간을 낭비했음을 알기 위해 대규모 프로그램에서 모든 것을 변경하고 싶지 않습니다.

짧은 답변

네, 틀 렸습니다. 대부분의 경우 사용 공간 측면에서 거의 차이없습니다 .

최적화가 필요하다는 명확한 증거가 없다면이를 최적화하는 것은 가치없습니다 . 특히 개체 필드의 메모리 사용을 최적화 해야하는 경우 다른 (더 효과적인) 조치를 취해야 할 것입니다.

더 긴 답변

Java Virtual Machine은 32 비트 기본 셀 크기의 배수 인 오프셋을 사용하여 스택 및 오브젝트 필드를 모델링합니다. 따라서 지역 변수 또는 객체 필드를 (예를 들어) a로 선언 byte하면 변수 / 필드가 int.

이에 대한 두 가지 예외가 있습니다.

  • longdouble값에는 2 개의 기본 32 비트 셀이 필요합니다.
  • 기본 유형의 배열은 압축 된 형식으로 표시되므로 (예를 들어) 바이트 배열은 32 비트 워드 당 4 바이트를 보유합니다.

따라서 및 … 및 대규모 기본 요소 배열 사용을 최적화 할 가치 있습니다 . 그러나 일반적으로 아닙니다.longdouble

이론적으로, JIT는 수도 이를 최적화 할 수 있지만, 실제로 내가 수행하는 JIT 들어 본 적이없는. 한 가지 장애는 컴파일되는 클래스의 인스턴스가 생성 될 때까지 JIT가 일반적으로 실행될 수 없다는 것입니다. JIT가 메모리 레이아웃을 최적화했다면 동일한 클래스의 객체에 대해 두 개 (또는 그 이상)의 “향기”를 가질 수 있으며 이는 큰 어려움을 초래할 것입니다.


재검토

벤치 마크 결과 @meriton의 대답을 살펴보면 곱셈에 대한 성능 저하가 발생하는 대신 short및 사용하는 것으로 보입니다 . 실제로, 작업을 개별적으로 고려하면 벌금이 상당합니다. (단독으로 고려해서는 안되지만 … 그것은 또 다른 주제입니다.)byteint

설명은 JIT가 각 경우에 32 비트 곱셈 명령을 사용하여 곱셈을 수행하고 있다는 것입니다. 그러나 byteshort경우 에는 중간 32 비트 값을 각 루프 반복에서 또는 로 변환하는 추가 명령을 실행 합니다. (이론적으로이 변환은 루프의 끝에서 한 번만 수행 할 수 있지만 최적화 프로그램이이를 파악할 수 있을지 의심 스럽습니다.)byteshort

어쨌든,이 전환의 또 다른 문제에 지점을 수행 short하고 byte최적화한다. 산술과 연산 집약적 인 알고리즘에서 성능을 악화 시킬 수 있습니다.


답변

이는 JVM의 구현과 기본 하드웨어에 따라 다릅니다. 대부분의 최신 하드웨어는 메모리 (또는 첫 번째 레벨 캐시에서도)에서 단일 바이트를 가져 오지 않습니다. 즉, 더 작은 기본 유형을 사용한다고해서 일반적으로 메모리 대역폭 소비가 줄어들지 않습니다. 마찬가지로 최신 CPU의 워드 크기는 64 비트입니다. 더 적은 비트에 대해 작업을 수행 할 수 있지만 여분의 비트를 버리는 방식으로 작동하지만 빠르지도 않습니다.

유일한 이점은 더 작은 기본 유형이 더 컴팩트 한 메모리 레이아웃을 생성 할 수 있다는 것입니다. 특히 배열을 사용할 때 더욱 그렇습니다. 이는 메모리를 절약하여 참조 지역성을 개선하고 (따라서 캐시 누락 수를 줄임) 가비지 콜렉션 오버 헤드를 줄일 수 있습니다.

그러나 일반적으로 더 작은 기본 유형을 사용하는 것이 더 빠르지 않습니다.

이를 입증하려면 다음 벤치 마크를 참조하십시오.

package tools.bench;

import java.math.BigDecimal;

public abstract class Benchmark {

    final String name;

    public Benchmark(String name) {
        this.name = name;
    }

    abstract int run(int iterations) throws Throwable;

    private BigDecimal time() {
        try {
            int nextI = 1;
            int i;
            long duration;
            do {
                i = nextI;
                long start = System.nanoTime();
                run(i);
                duration = System.nanoTime() - start;
                nextI = (i << 1) | 1;
            } while (duration < 100000000 && nextI > 0);
            return new BigDecimal((duration) * 1000 / i).movePointLeft(3);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public String toString() {
        return name + "\t" + time() + " ns";
    }

    public static void main(String[] args) throws Exception {
        Benchmark[] benchmarks = {
            new Benchmark("int multiplication") {
                @Override int run(int iterations) throws Throwable {
                    int x = 1;
                    for (int i = 0; i < iterations; i++) {
                        x *= 3;
                    }
                    return x;
                }
            },
            new Benchmark("short multiplication") {
                @Override int run(int iterations) throws Throwable {
                    short x = 0;
                    for (int i = 0; i < iterations; i++) {
                        x *= 3;
                    }
                    return x;
                }
            },
            new Benchmark("byte multiplication") {
                @Override int run(int iterations) throws Throwable {
                    byte x = 0;
                    for (int i = 0; i < iterations; i++) {
                        x *= 3;
                    }
                    return x;
                }
            },
            new Benchmark("int[] traversal") {
                @Override int run(int iterations) throws Throwable {
                    int[] x = new int[iterations];
                    for (int i = 0; i < iterations; i++) {
                        x[i] = i;
                    }
                    return x[x[0]];
                }
            },
            new Benchmark("short[] traversal") {
                @Override int run(int iterations) throws Throwable {
                    short[] x = new short[iterations];
                    for (int i = 0; i < iterations; i++) {
                        x[i] = (short) i;
                    }
                    return x[x[0]];
                }
            },
            new Benchmark("byte[] traversal") {
                @Override int run(int iterations) throws Throwable {
                    byte[] x = new byte[iterations];
                    for (int i = 0; i < iterations; i++) {
                        x[i] = (byte) i;
                    }
                    return x[x[0]];
                }
            },
        };
        for (Benchmark bm : benchmarks) {
            System.out.println(bm);
        }
    }
}

내 다소 오래된 노트북에 인쇄됩니다 (열을 조정하기 위해 공백 추가).

int       multiplication    1.530 ns
short     multiplication    2.105 ns
byte      multiplication    2.483 ns
int[]     traversal         5.347 ns
short[]   traversal         4.760 ns
byte[]    traversal         2.064 ns

보시다시피 성능 차이는 매우 작습니다. 알고리즘 최적화는 기본 유형을 선택하는 것보다 훨씬 더 중요합니다.


답변

byte대신 사용 int하면 많은 양을 사용하는 경우 성능이 향상 될 수 있습니다. 다음은 실험입니다.

import java.lang.management.*;

public class SpeedTest {

/** Get CPU time in nanoseconds. */
public static long getCpuTime() {
    ThreadMXBean bean = ManagementFactory.getThreadMXBean();
    return bean.isCurrentThreadCpuTimeSupported() ? bean
            .getCurrentThreadCpuTime() : 0L;
}

public static void main(String[] args) {
    long durationTotal = 0;
    int numberOfTests=0;

    for (int j = 1; j < 51; j++) {
        long beforeTask = getCpuTime();
        // MEASURES THIS AREA------------------------------------------
        long x = 20000000;// 20 millions
        for (long i = 0; i < x; i++) {
                           TestClass s = new TestClass();

        }
        // MEASURES THIS AREA------------------------------------------
        long duration = getCpuTime() - beforeTask;
        System.out.println("TEST " + j + ": duration = " + duration + "ns = "
                + (int) duration / 1000000);
        durationTotal += duration;
        numberOfTests++;
    }
    double average = durationTotal/numberOfTests;
    System.out.println("-----------------------------------");
    System.out.println("Average Duration = " + average + " ns = "
            + (int)average / 1000000 +" ms (Approximately)");


}

}

이 클래스는 새 TestClass. 각 테스트는 2 천만 번 수행하며 50 개의 테스트가 있습니다.

다음은 TestClass입니다.

 public class TestClass {
     int a1= 5;
     int a2= 5;
     int a3= 5;
     int a4= 5;
     int a5= 5;
     int a6= 5;
     int a7= 5;
     int a8= 5;
     int a9= 5;
     int a10= 5;
     int a11= 5;
     int a12=5;
     int a13= 5;
     int a14= 5;
 }

나는 SpeedTest수업을 운영했고 결국 이것을 얻었습니다.

 Average Duration = 8.9625E8 ns = 896 ms (Approximately)

이제 TestClass에서 int를 바이트로 변경하고 다시 실행합니다. 결과는 다음과 같습니다.

 Average Duration = 6.94375E8 ns = 694 ms (Approximately)

이 실험은 엄청난 양의 변수를 인스턴스화하는 경우 int 대신 byte를 사용하면 효율성을 높일 수 있음을 보여줍니다.


답변

일반적으로 바이트는 8 비트로 간주됩니다. short는 일반적으로 16 비트로 간주됩니다.

“순수한”환경에서, 모든 바이트와 long, shorts 및 기타 재미있는 것들이 일반적으로 숨겨져 있기 때문에 자바가 아니라면 byte는 공간을 더 잘 사용합니다.

그러나 컴퓨터는 아마도 8 비트가 아니고 16 비트도 아닐 것입니다. 이는 특히 16 비트 또는 8 비트를 얻으려면 필요할 때 이러한 유형에 액세스 할 수있는 것처럼 가장하기 위해 시간을 낭비하는 “속임수”에 의존해야 함을 의미합니다.

이 시점에서 하드웨어 구현 방법에 따라 다릅니다. 그러나 내가 힘들었던 것으로부터 최고의 속도는 CPU가 사용하기에 편안한 청크로 물건을 저장하는 것에서 달성됩니다. 64 비트 프로세서는 64 비트 요소를 처리하는 것을 좋아하며, 그보다 적은 것은 종종 처리하는 것을 좋아하는 척하기 위해 “엔지니어링 마법”이 필요합니다.


답변

short / byte / char의 성능이 떨어지는 이유 중 하나는 이러한 데이터 유형에 대한 직접적인 지원이 부족하기 때문입니다. 직접 지원한다는 것은 JVM 사양이 이러한 데이터 유형에 대한 명령 세트를 언급하지 않음을 의미합니다. 저장,로드, 추가 등과 같은 명령어에는 int 데이터 유형에 대한 버전이 있습니다. 그러나 short / byte / char 버전은 없습니다. 예를 들어 아래 자바 코드를 고려하십시오.

void spin() {
 int i;
 for (i = 0; i < 100; i++) {
 ; // Loop body is empty
 }
}

동일한 것은 아래와 같이 기계어 코드로 변환됩니다.

0 iconst_0 // Push int constant 0
1 istore_1 // Store into local variable 1 (i=0)
2 goto 8 // First time through don't increment
5 iinc 1 1 // Increment local variable 1 by 1 (i++)
8 iload_1 // Push local variable 1 (i)
9 bipush 100 // Push int constant 100
11 if_icmplt 5 // Compare and loop if less than (i < 100)
14 return // Return void when done

이제 다음과 같이 int를 short로 변경하는 것을 고려하십시오.

void sspin() {
 short i;
 for (i = 0; i < 100; i++) {
 ; // Loop body is empty
 }
}

해당 기계어 코드는 다음과 같이 변경됩니다.

0 iconst_0
1 istore_1
2 goto 10
5 iload_1 // The short is treated as though an int
6 iconst_1
7 iadd
8 i2s // Truncate int to short
9 istore_1
10 iload_1
11 bipush 100
13 if_icmplt 5
16 return

보시다시피 짧은 데이터 유형을 조작하기 위해 여전히 int 데이터 유형 명령어 버전을 사용하고 필요할 때 int를 short로 명시 적으로 변환합니다. 이로 인해 성능이 저하됩니다.

이제 직접 지원하지 않는 이유는 다음과 같습니다.

Java Virtual Machine은 int 유형의 데이터에 대한 가장 직접적인 지원을 제공합니다. 이것은 부분적으로 Java Virtual Machine의 피연산자 스택 및 지역 변수 배열의 효율적인 구현을 기대합니다. 또한 일반적인 프로그램에서 int 데이터의 빈도에 의해 동기가 부여됩니다. 다른 정수 유형은 직접적인 지원이 적습니다. 예를 들어, 저장,로드 또는 추가 명령어의 바이트, 문자 또는 짧은 버전이 없습니다.

여기에있는 JVM 사양에서 인용 (페이지 58).


답변

그 차이는 거의 눈에 띄지 않습니다! 디자인, 적절성, 균일 성, 습관 등의 문제에 더 가깝습니다. 때로는 맛의 문제 일 수도 있습니다. 당신이 신경 쓰는 모든 것이 당신의 프로그램이 시작되고 실행되고 a float를으로 대체 int해도 정확성에 해를 끼치 지 않는 것이라면, 어느 유형을 사용하더라도 성능이 변한다는 것을 증명할 수 없다면 나는 둘 중 하나를 사용하는 데 아무런 이점이 없다고 생각합니다. 2 바이트 또는 3 바이트가 다른 유형을 기반으로 성능을 조정하는 것이 실제로 가장 중요합니다. Donald Knuth는 “조기 최적화는 모든 악의 근원입니다”라고 말했습니다 (그 사람인지 확실하지 않습니다. 답이 있으면 편집하십시오).


답변