programing

자바에서는 int와 float 대신 byte를 사용하는 것이 더 효율적입니까?

newsource 2022. 11. 26. 08:38

자바에서는 int와 float 대신 byte를 사용하는 것이 더 효율적입니까?

숫자가 아무리 작든 크든 항상 int와 double을 사용한다는 것을 알게 되었습니다.에서는 '자바'를 사용하는 더요?byte ★★★★★★★★★★★★★★★★★」shortint ★★★★★★★★★★★★★★★★★」floatdouble

그러니까 내가 ints와 double이 많은 프로그램을 가지고 있다고 가정해봐.이 숫자가 맞는다는 것을 안다면 내 int를 bytes나 short로 바꿀 가치가 있을까요?

java는 서명되지 않은 타입이 없는 것은 알고 있습니다만, 플러스만 될 것으로 알고 있으면 다른 방법이 있습니까?

효율이란 주로 가공을 의미합니다.모든 변수가 반사이즈로 되어 계산도 빨라진다면 가비지 콜렉터가 훨씬 빠를 것이라고 생각합니다.(안드로이드 작업 중이라 RAM도 조금 걱정해야 할 것 같습니다.

(가비지 컬렉터는 오브젝트만 취급하고 프리미티브는 취급하지 않지만 포기된 오브젝트의 프리미티브는 모두 삭제한다고 생각합니다.)

가지고 있는 작은 안드로이드 앱으로 시도해 봤지만 전혀 차이를 느끼지 못했습니다.('과학적으로' 측정하지는 않았지만)

더 빠르고 효율적이라고 생각하는 게 잘못된 건가요?내가 시간을 낭비했다는 걸 알기 위해 거대한 프로그램의 모든 것을 바꾸고 싶진 않아.

새로운 프로젝트를 시작할 때 처음부터 할 가치가 있을까요?(모든 작은 부분들이 도움이 될 것 같지만, 만약 그렇다면 왜 아무도 그렇게 하지 않는 것 같습니까?)

더 빠르고 효율적이라고 생각하는 게 잘못된 건가요?내가 시간을 낭비했다는 걸 알기 위해 거대한 프로그램의 모든 것을 바꾸고 싶진 않아.

단답

네, 당신이 틀렸어요.대부분의 경우 사용되는 공간에는 거의 차이가 없습니다.

이것을 최적화하려고 할 필요는 없습니다.최적화가 필요하다는 명확한 증거가 없는 한.특히 오브젝트 필드의 메모리 사용량을 최적화할 필요가 있는 경우에는 다른 (더 효과적인) 조치를 취해야 할 수도 있습니다.

더 긴 답변

Java Virtual Machine 모델은 32비트 원시 셀 크기의 (실제로) 배수인 오프셋을 사용하여 스택 및 개체 필드를 모델링합니다.를 들어때, 「」( 「」)로 선언합니다.byte: 32비트).int.

여기에는 두 가지 예외가 있습니다.

  • long ★★★★★★★★★★★★★★★★★」double의 원시 합니다.
  • (예를 들어) 바이트 배열이 32비트 워드당 4바이트를 유지할 수 있도록 원시 유형의 배열이 팩 형식으로 표시됩니다.

따라서 이 기능을 최적화할 가치가 있을 수 있습니다.long ★★★★★★★★★★★★★★★★★」double 원시 의 대규모 ...또한 원시 요소의 대규모 배열입니다.하지만 일반적으로는 아니다.

이론적으로는 JIT가 이것을 최적화할 수 있을지도 모릅니다만, 실제로 최적화할 수 있는 JIT는 들어 본 적이 없습니다.한 가지 장애물은 컴파일 중인 클래스의 인스턴스가 생성될 때까지 일반적으로 JIT를 실행할 수 없다는 것입니다.JIT에 의해 메모리 레이아웃이 최적화되면 같은 클래스의 오브젝트의 "플래버"가 2개 이상 있을 수 있습니다.그것은 큰 문제가 됩니다.


재방문

@ @meriton을 사용하고 있는 것 .short ★★★★★★★★★★★★★★★★★」byteint는 곱셈에 대해 퍼포먼스 패널티를 부과합니다.실제로, 운용을 개별적으로 생각하면, 큰 메리트가 있습니다.(이러한 운용을 개별적으로 생각하면 안 됩니다만, 이것은 다른 토픽입니다.)

아마 JIT는 각각의 경우에 32비트 곱셈 명령을 사용하여 곱셈을 하고 있을 것이라고 생각합니다. 하만 byte ★★★★★★★★★★★★★★★★★」short이 경우, 중간 32비트 값을 다른 값으로 변환하기 위한 추가 명령을 실행합니다.byte ★★★★★★★★★★★★★★★★★」short에서 한 번할 수 , 입니다.)(이론적으로는 이 변환은 루프의 끝에서 한 번 할 수 있지만 최적화 툴이 그것을 알아낼 수 있을지는 의문입니다.)

어쨌든, 이것은, 로의 전환에 관한 또 다른 문제를 나타내고 있습니다.short ★★★★★★★★★★★★★★★★★」byte최적화로서.산술과 계산 부하가 높은 알고리즘에서는 퍼포먼스가 저하될 가능성이 있습니다.


보조 질문

java는 서명되지 않은 타입이 없는 것은 알고 있습니다만, 플러스만 될 것으로 알고 있으면 다른 방법이 있습니까?

에서는 안 (에는 몇 요. 이치노Integer,Long 대처하기 , int,long츠요시그러나 이러한 기능들은 성능상의 이점을 제공하지 않습니다.을 참조해 주십시오.)

(가비지 컬렉터는 오브젝트만 취급하고 프리미티브는 취급하지 않지만 포기된 오브젝트의 프리미티브는 모두 삭제한다고 생각합니다.)

맞아요.객체의 필드는 객체의 일부입니다.개체가 가비지 수집되면 사라집니다.마찬가지로 어레이가 수집되면 어레이의 셀이 사라집니다.필드 또는 셀 유형이 원시 유형인 경우 값은 필드/셀에 저장됩니다.오브젝트/어레이의 일부이며 삭제되어 있습니다.

이는 기본 하드웨어뿐만 아니라 JVM의 구현에 따라 달라집니다.대부분의 최신 하드웨어는 메모리(또는 첫 번째 수준의 캐시)에서1바이트를 취득하지 않습니다.즉, 일반적으로 작은 원시 타입을 사용해도 메모리 대역폭 소비량은 감소하지 않습니다.마찬가지로 최신 CPU의 워드 사이즈는 64비트입니다.더 적은 비트로 작업을 수행할 수 있지만, 이는 여분의 비트를 폐기함으로써 작동하며, 이 속도도 빠르지 않습니다.

유일한 장점은 원시 타입이 작을 경우, 특히 어레이를 사용할 때 메모리 레이아웃을 더 콤팩트하게 만들 수 있다는 것입니다.이렇게 하면 메모리가 절약되므로 참조 인접성이 향상되고(따라서 캐시 누락 수가 감소), 가비지 수집 오버헤드가 줄어듭니다.

그러나 일반적으로 작은 원시형을 사용하는 것이 빠르지는 않다.

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

public class Benchmark {

    public static void benchmark(String label, Code code) {
        print(25, label);
        
        try {
            for (int iterations = 1; ; iterations *= 2) { // detect reasonable iteration count and warm up the code under test
                System.gc(); // clean up previous runs, so we don't benchmark their cleanup
                long previouslyUsedMemory = usedMemory();
                long start = System.nanoTime();
                code.execute(iterations);
                long duration = System.nanoTime() - start;
                long memoryUsed = usedMemory() - previouslyUsedMemory;
                
                if (iterations > 1E8 || duration > 1E9) { 
                    print(25, new BigDecimal(duration * 1000 / iterations).movePointLeft(3) + " ns / iteration");
                    print(30, new BigDecimal(memoryUsed * 1000 / iterations).movePointLeft(3) + " bytes / iteration\n");
                    return;
                }
            }
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }
    
    private static void print(int desiredLength, String message) {
        System.out.print(" ".repeat(Math.max(1, desiredLength - message.length())) + message);
    }
    
    private static long usedMemory() {
        return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
    }

    @FunctionalInterface
    interface Code {
        /**
         * Executes the code under test.
         * 
         * @param iterations
         *            number of iterations to perform
         * @return any value that requires the entire code to be executed (to
         *         prevent dead code elimination by the just in time compiler)
         * @throws Throwable
         *             if the test could not complete successfully
         */
        Object execute(int iterations);
    }

    public static void main(String[] args) {
        benchmark("long[] traversal", (iterations) -> {
            long[] array = new long[iterations];
            for (int i = 0; i < iterations; i++) {
                array[i] = i;
            }
            return array;
        });
        benchmark("int[] traversal", (iterations) -> {
            int[] array = new int[iterations];
            for (int i = 0; i < iterations; i++) {
                array[i] = i;
            }
            return array;
        });
        benchmark("short[] traversal", (iterations) -> {
            short[] array = new short[iterations];
            for (int i = 0; i < iterations; i++) {
                array[i] = (short) i;
            }
            return array;
        });
        benchmark("byte[] traversal", (iterations) -> {
            byte[] array = new byte[iterations];
            for (int i = 0; i < iterations; i++) {
                array[i] = (byte) i;
            }
            return array;
        });
        
        benchmark("long fields", (iterations) -> {
            class C {
                long a = 1;
                long b = 2;
            }
            
            C[] array = new C[iterations];
            for (int i = 0; i < iterations; i++) {
                array[i] = new C();
            }
            return array;
        });
        benchmark("int fields", (iterations) -> {
            class C {
                int a = 1;
                int b = 2;
            }
            
            C[] array = new C[iterations];
            for (int i = 0; i < iterations; i++) {
                array[i] = new C();
            }
            return array;
        });
        benchmark("short fields", (iterations) -> {
            class C {
                short a = 1;
                short b = 2;
            }
            
            C[] array = new C[iterations];
            for (int i = 0; i < iterations; i++) {
                array[i] = new C();
            }
            return array;
        });
        benchmark("byte fields", (iterations) -> {
            class C {
                byte a = 1;
                byte b = 2;
            }
            
            C[] array = new C[iterations];
            for (int i = 0; i < iterations; i++) {
                array[i] = new C();
            }
            return array;
        });

        benchmark("long multiplication", (iterations) -> {
            long result = 1;
            for (int i = 0; i < iterations; i++) {
                result *= 3;
            }
            return result;
        });
        benchmark("int multiplication", (iterations) -> {
            int result = 1;
            for (int i = 0; i < iterations; i++) {
                result *= 3;
            }
            return result;
        });
        benchmark("short multiplication", (iterations) -> {
            short result = 1;
            for (int i = 0; i < iterations; i++) {
                result *= 3;
            }
            return result;
        });
        benchmark("byte multiplication", (iterations) -> {
            byte result = 1;
            for (int i = 0; i < iterations; i++) {
                result *= 3;
            }
            return result;
        });
    }
}

인텔 Core i7 CPU에서 OpenJDK 14를 3.5GHz로 실행하면 다음과 같이 출력됩니다.

     long[] traversal     3.206 ns / iteration      8.007 bytes / iteration
      int[] traversal     1.557 ns / iteration      4.007 bytes / iteration
    short[] traversal     0.881 ns / iteration      2.007 bytes / iteration
     byte[] traversal     0.584 ns / iteration      1.007 bytes / iteration
          long fields    25.485 ns / iteration     36.359 bytes / iteration
           int fields    23.126 ns / iteration     28.304 bytes / iteration
         short fields    21.717 ns / iteration     20.296 bytes / iteration
          byte fields    21.767 ns / iteration     20.273 bytes / iteration
  long multiplication     0.538 ns / iteration      0.000 bytes / iteration
   int multiplication     0.526 ns / iteration      0.000 bytes / iteration
 short multiplication     0.786 ns / iteration      0.000 bytes / iteration
  byte multiplication     0.784 ns / iteration      0.000 bytes / iteration

보시다시피 대규모 어레이를 통과할 때 속도가 크게 절감되는 것은 객체 필드가 작을 경우 이점이 거의 없으며 데이터 유형이 작을 경우 계산 속도가 실제로 약간 느려집니다.

전반적으로 성능 차이는 매우 작습니다.알고리즘을 최적화하는 것은 원시 유형을 선택하는 것보다 훨씬 더 중요합니다.

「」를 사용합니다.byteint대량으로 사용하면 퍼포먼스가 향상됩니다.다음은 실험입니다.

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개의 테스트가 있습니다.

Test Class는 다음과 같습니다.

 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에서 ints를 바이트로 변경하고 다시 실행합니다.결과는 다음과 같습니다.

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

이 실험은 만약 당신이 엄청난 양의 변수를 인스턴스화한다면, int 대신 바이트를 사용하는 것이 효율을 높일 수 있다는 것을 보여준다고 믿는다.

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

"순수한" 환경에서는 바이트, 롱, 쇼트 등의 모든 구현이 Java가 아니며 일반적으로 바이트가 공간을 더 잘 활용합니다.

그러나 컴퓨터는 8비트가 아닐 수도 있고 16비트가 아닐 수도 있습니다.이는 특히 16 또는 8비트를 얻기 위해서는 필요할 때 이러한 유형에 액세스할 수 있는 것처럼 가장하기 위해 시간을 낭비하는 "꼼수"에 의존해야 한다는 것을 의미합니다.

이 시점에서는 하드웨어 구현 방법에 따라 달라집니다.하지만 제가 들은 바로는 CPU가 사용하기 쉬운 청크로 저장함으로써 최고의 속도를 얻을 수 있습니다.64비트 프로세서는 64비트 요소를 다루는 것을 좋아하며, 그 미만이면 종종 "엔지니어링 매직"을 필요로 합니다.

short/byte/char의 퍼포먼스가 떨어지는 이유 중 하나는 이러한 데이터 유형을 직접 지원하지 않기 때문입니다.즉, JVM 사양에서는 이러한 데이터 유형에 대한 명령 세트를 일절 언급하지 않습니다.저장, 로드, 추가 등의 지시사항int 데이터형 버전이 있습니다.단, short/byte/char 버전은 없습니다.예: 아래 Java 코드를 고려하십시오.

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페이지).

나는 수용된 답변은 다소 잘못된 것이라고 말하고 싶다. "사용하는 공간의 차이는 거의 없다."다음으로 경우에 따라 차이가 크게 다르다는 것을 보여주는 예를 제시하겠습니다.

Baseline usage 4.90MB, java: 11.0.12
Mem usage - bytes : +202.60 MB
Mem usage - shorts: +283.02 MB
Mem usage - ints  : +363.02 MB
Mem usage - bytes : +203.02 MB
Mem usage - shorts: +283.02 MB
Mem usage - ints  : +363.02 MB
Mem usage - bytes : +203.02 MB
Mem usage - shorts: +283.02 MB
Mem usage - ints  : +363.02 MB

확인할 코드:

static class Bytes {
    public byte f1;
    public byte f2;
    public byte f3;
    public byte f4;
}

static class Shorts {
    public short f1;
    public short f2;
    public short f3;
    public short f4;
}

static class Ints {
    public int f1;
    public int f2;
    public int f3;
    public int f4;
}

@Test
public void memUsageTest() throws Exception {
    int countOfItems = 10 * 1024 * 1024;
    float MB = 1024*1024;
    Runtime rt = Runtime.getRuntime();

    System.gc();
    Thread.sleep(1000);
    long baseLineUsage = rt.totalMemory() - rt.freeMemory();

    trace("Baseline usage %.2fMB, java: %s", (baseLineUsage / MB), System.getProperty("java.version"));

    for( int j = 0; j < 3; j++ ) {
        Bytes[] bytes = new Bytes[countOfItems];
        for( int i = 0; i < bytes.length; i++ ) {
            bytes[i] = new Bytes();
        }
        System.gc();
        Thread.sleep(1000);
        trace("Mem usage - bytes : +%.2f MB", (rt.totalMemory() - rt.freeMemory() - baseLineUsage) / MB);
        bytes = null;

        Shorts[] shorts = new Shorts[countOfItems];
        for( int i = 0; i < shorts.length; i++ ) {
            shorts[i] = new Shorts();
        }
        System.gc();
        Thread.sleep(1000);
        trace("Mem usage - shorts: +%.2f MB", (rt.totalMemory() - rt.freeMemory() - baseLineUsage) / MB);
        shorts = null;

        Ints[] ints = new Ints[countOfItems];
        for( int i = 0; i < ints.length; i++ ) {
            ints[i] = new Ints();
        }
        System.gc();
        Thread.sleep(1000);
        trace("Mem usage - ints  : +%.2f MB", (rt.totalMemory() - rt.freeMemory() - baseLineUsage) / MB);
        ints = null;
    }
}

private static void trace(String message, Object... args) {
    String line = String.format(US, message, args);
    System.out.println(line);
}

차이는 거의 눈에 띄지 않는다!디자인, 적절성, 획일성, 습관 등에 대한 질문입니다.가끔 그건 그냥 취향의 문제일 뿐이야.프로그램이 가동되어 가동되고 있는 것 외에float잠 an an int정확성을 해치지 않습니다.어느 타입이든 퍼포먼스가 변화한다는 것을 증명할 수 없는 한 어느 쪽을 선택해도 좋을 것 같지 않습니다.2바이트 또는 3바이트가 다른 타입에 따라 퍼포먼스를 조정하는 것은 결코 해서는 안 됩니다.Donald Knuth는 "프리미처 최적화는 모든 악의 근원"이라고 말한 적이 있습니다(자신이 맞는지 확신할 수 없습니다.정답이 있으면 편집하십시오).

언급URL : https://stackoverflow.com/questions/14531235/in-java-is-it-more-efficient-to-use-byte-or-short-instead-of-int-and-float-inst