알고리즘/성능 분석 및 최적화

[Java] 기본형과 래퍼형 성능 비교

go_getter 2025. 2. 25. 21:07

테스트 방법

  • 기본형과 래퍼형 모두 1000만 번의 반복문을 돌면서 연산을 진행한다.
  • `System.nanoTime()` 메서드를 사용해서 반복문의 전, 후 사이의 시간을 기록한다.

1. 기본 자료형 int와 래퍼 클래스 Integer 연산 성능 테스트 코드

public class PrimitiveVsWrapperPerformance {
    public static void main(String[] args) {
        int iteration = 10_000_000; // 1000만 번 반복
        
        // 기본형 int 연산 성능 테스트
        long startTime1 = System.nanoTime();
        int sum1 = 0;
        for (int i = 0; i < iteration; i++) {
            sum1 += i;  // 기본형 연산
        }
        long endTime1 = System.nanoTime();
        long primitiveTime = endTime1 - startTime1;

        // 래퍼 클래스 Integer 연산 성능 테스트
        long startTime2 = System.nanoTime();
        Integer sum2 = 0;
        for (int i = 0; i < iteration; i++) {
            sum2 += i;  // 오토박싱 & 언박싱 발생
        }
        long endTime2 = System.nanoTime();
        long wrapperTime = endTime2 - startTime2;

        // 결과 출력
        System.out.println("기본형(int) 연산 시간: " + primitiveTime + " ns");
        System.out.println("래퍼 클래스(Integer) 연산 시간: " + wrapperTime + " ns");
        System.out.println("성능 차이 (배수): " + (double) wrapperTime / primitiveTime);
    }
}

 

 

2. 기본 자료형 double과 직접 만든 래퍼형 MyDouble의 연산 성능 테스트 코드

2-1.

package chap1;

public class MyDouble {
    double value;

    public MyDouble(Double value){
        this.value = value;
    } //기본 생성자

    public Double getValue(){
        return value;
    }

    public void setValue(Double value){
        this.value = value;
    }

    public void sumValue(int num){
        this.value += num;
    }
}
package chap1;

public class PrimitiveVsWrapperPerformance {
    public static void main(String[] args) {
        int iterator = 10_000_000;

        // 기본 Double 성능 테스트
        double sum1 = 0.0;

        long startTime1 = System.nanoTime();
        for(int i=0;i<iterator;i++){
            sum1+=i;
        }
        long endTime1 = System.nanoTime();

        long totalTime1 = endTime1 - startTime1;


        // 내가 만든 MyDouble 성능 테스트
        MyDouble sum2 = new MyDouble(0.0);

        long startTime2 = System.nanoTime();
        for(int i=0;i<iterator;i++){
            sum2.sumValue(i);
        }
        long endTime2 = System.nanoTime();

        long totalTime2 = endTime2 - startTime2;

        System.out.println("totalTime1 = " + totalTime1);
        System.out.println("totalTime2 = " + totalTime2);
    }
}

// totalTime1 = 3683125
// totalTime2 = 24226291

 

계산했을 때, 래퍼형이 약 8배 가량 연산 시간이 오래 걸리는 것을 볼 수 있다.

객체를 언박싱해서 매개변수와 연산하고, 다시 그 값을 박싱하는 과정이 필요하기 때문이다.

래퍼형을 다른 방식으로 연산했을 때는 어떤 결과값이 있는지 확인해보자.

 

 

2-2.

// 직접 만든 Double 래퍼 클래스
class MyDouble {
    private final double value;

    public MyDouble(double value) {
        this.value = value;
    }

    public double getValue() {
        return value;
    }

    // 덧셈
    public MyDouble add(MyDouble other) {
        return new MyDouble(this.value + other.value);
    }

    @Override
    public String toString() {
        return String.valueOf(value);
    }
}
public class DoubleWrapperPerformance {
    public static void main(String[] args) {
        int iteration = 10_000_000; // 1000만 번 반복

        // 1. 기본형 double 연산
        long startTime1 = System.nanoTime();
        double sum1 = 0.0;
        for (int i = 0; i < iteration; i++) {
            sum1 += i * 1.1;
        }
        long endTime1 = System.nanoTime();
        long primitiveTime = endTime1 - startTime1;

        // 2. MyDouble 연산
        long startTime3 = System.nanoTime();
        MyDouble sum3 = new MyDouble(0.0);
        for (int i = 0; i < iteration; i++) {
            sum3 = sum3.add(new MyDouble(i * 1.1));
        }
        long endTime3 = System.nanoTime();
        long myDoubleTime = endTime3 - startTime3;

        // 결과 출력
        System.out.println("기본형(double) 연산 시간: " + primitiveTime + " ns");
        System.out.println("MyDouble 클래스 연산 시간: " + myDoubleTime + " ns");
        System.out.println("MyDouble vs double 성능 차이: " + (double) myDoubleTime / primitiveTime);
    }
}

// totalTime1 = 3717209
// totalTime2 = 103633583

 

이 경우는 약 28배 가량 래퍼형의 연산이 늦었다.

반복문 안에서 add 함수를 실행할 때마다 값을 박싱한 새로운 객체를 생성하고 , 기존의 객체와 새로운 객체를 언박싱하여 연산 후 다시 박싱하는 과정을 거치기 때문에 시간이 매우 지연된다.

그리고 이 코드는 시간의 문제 뿐만 아니라, 1000만 번의 반복문을 실행하면서 1000만 개의 새로운 객체를 생성하게 되면서 아래와 같은 상황을 발생시킬 수 있다.

 

1. 메모리 부족 (OutOfMemoryError)

  • 힙 메모리를 초과하면 OutOfMemoryError 가 발생할 수 있다.

2. 가비지 컬렉션(GC) 부담 증가

  • 가비지 컬렉션이 불필요한 객체를 자동으로 정리하지만, 객체가 너무 많으면 GC 작업이 잦아져 성능 저하가 발생할 수 있다.
  • 잦은 GC → CPU 부하 증가 → 애플리케이션 응답성 저하

3. 메모리 단편화

  • 많은 객체가 생성되고 해제되면서 메모리가 조각나게 되고, 결국 새로운 객체 할당 시 성능 저하의 원인이 될 수 있다.

4. 캐시 미스 증가

  • 객체가 너무 많으면 CPU 캐시에서 데이터를 효율적으로 관리하기 어려워져 캐시 미스(cache miss) 빈도가 증가 → 실행 속도 저하

 


결론

래퍼 클래스는 아래 게시물과 같은 장점을 가지고 있지만, 위와 같은 시간 지연과 메모리 차지 등의 문제점을 일으키기 때문에 상황에 맞게 사용하도록 하자.

 

2025.02.25 - [언어/Java] - Wrapper 클래스

 

Wrapper 클래스

Wrapper Class기본 자료형을 객체로 감싸는 클래스즉, 래퍼 클래스는 객체  객체 또는 변수가 담겨있는, 참조형 변수는 데이터의 메모리 주소를 저장하므로, 해당 변수를 출력하면 데이터의 메모

go-getter1kim.tistory.com