본문 바로가기
우아한테크코스 4기/레벨1

[Java] 인스턴스를 캐싱하여 성능 개선하기 (feat. 로또)

by 나는후니 2022. 2. 23.

오늘 포스팅은 빈번하게 사용되는 인스턴스를 캐싱하여 성능을 개선하는 방법에 대해 작성해보고자 합니다.

 

정적 팩토리 메소드의 장점을 떠올리면 이름을 가질 수 있다 가 가장 먼저 떠오릅니다. 하지만 정적 팩토리 메소드에는 또 다른 장점이 존재합니다.

바로 자주 사용하는 인스턴스를 캐싱하여 성능을 개선하는 방법인데요.

 

Integer 코드를 통해 이 내용을 확인해봅시다.

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

Integer의 정적 팩토리 메서드인 valueOf 메서드의 구현 내용을 보면 매개변수 i가 Cache된 값의 최소값과 최대값 사이에 있으면 새로운 인스턴스를 반환하는 것이 아닌 기존 캐시된 값을 가져오는 것을 확인할 수 있습니다.

1. Integer.valueOf(10) == Integer.valueOf(10)
2. Integer.valueOf(500) != Integer.valueOf(500)

IntegerCache의 범위는 -128 ~ 127 이기 때문에 위 비교에서 1번 코드 같은 경우에는 동일성이 보장됩니다.

하지만 캐시된 값이 존재하지 않는 2번 코드는 새로운 인스턴스를 생성하여 반환하기 때문에 동일성이 보장되지 않습니다.

이처럼 정적 팩토리 메서드를 활용하면 동일한 객체가 여러번 생성되는 경우 성능을 크게 개선할 수 있는 것입니다.

 

그렇다면 이 내용을 활용하여 로또 미션의 코드를 개선해봅시다.

로또 미션 개선하기

로또 번호의 범위는 1 ~ 45 입니다. 현재 요구사항에 따르면 범위 외의 값은 전혀 사용되지 않죠.

먼저 정적 팩토리 메서드를 사용하지 않은 상태의 코드를 확인해봅시다.

스크린샷 2022-02-23 오후 6 07 58

위 코드에서 보시다시피 일반 생성자를 통해 번호를 생성합니다.

로또 1 : 1, 2, 3, 4, 5, 6
로또 2 : 2, 4, 6, 7, 8, 9

하지만 로또 1과 로또 2 처럼 2, 4, 6의 수가 반복되어 생성된다면?
수백번 똑같은 LottoNumber가 생성되어 반환된다면 수백번 모두 새 인스턴스를 생성하게 되는 것입니다.

 

로또 번호는 45개밖에 존재하지 않습니다. 45개 수에 대한 빈번한 사용을 힌트로 성능을 개선할 수 있습니다.

이제 이 코드의 성능을 정적 팩토리 메서드를 사용하여 개선해봅시다.

먼저 1 ~ 45 number를 가진 LottoNumber를 배열에 캐싱해둡니다.

그리고 정적 팩토리 메서드를 통해 존재하는 인스턴스를 그대로 반환해줍니다. 이때 valueOf라는 이름을 명명함으로써
매개변수에 따라 인스턴스를 반환받을 수 있음을 명시적으로 표현해줄 수 있습니다.

 

이렇게 코드가 개선되면 최초 1 ~ 45 까지의 LottoNumber 인스턴스 생성 비용 외에 사용하는 비용이 없습니다.

덕분에 성능을 개선할 수 있는 것이죠

1000만회 45개 로또 번호 반환시킬 경우 사용 메모리, 시간
생성자 = 9397728 bytes
생성자 = 315 ms

정적 팩토리 메소드 = 2602568 bytes
정적 팩토리 메소드 = 32 ms

정리

정적 팩토리 메소드에는 사용이 빈번한 객체 캐싱을 통한 성능 개선의 기능도 있습니다.
제한된 값이 존재할 때는 인스턴스의 생성을 철저히 통제하면서 불변 값 객체에 대한 동일성을 보장해주기도 합니다.

위 사례처럼 적절한 시점에 사용한다면 정적 팩토리 메소드를 잘 활용할 수 있습니다!

 

이 개념을 상기시켜준 오리에게 Special Thanks To를 보냅니다.