• [이펙티브 코틀린] 7장 비용줄이기

    2023. 4. 30.

    by. 하루플스토리

    반응형

    7장 비용줄이기

    • 오늘날엔 코드의 효율성을 관대하게 바라봅니다. 이는 메모리는 저렴해졌고, 개발자는 비싸졌기 때문입니다.
    • 효율성은 중요하지만 최적화는 쉬운일이 아니다. 최적화는 초기 단계에서부터 하는 것은 얻는 것보다 잃는 것이 많은 경우가 있다. 그래도 프로그래밍에서는 잃을 것이 거의 없는 몇 가지 고정된 규칙이 있다.

    불필요한 객체 생성을 피하라

    • 객체 생성은 언제나 비용이 들어간다. 상황에 따라서는 굉장히 큰 비용이 들어갈 수도 있다. 따라서 불필요한 객체 생성을 피하는 것이 최적화의 관점에서 좋다.
    • 예를 들어 JVM에서 같은 문자열을 처리하는 코드가 여러개 있으면 기존의 문자열을 재사용한다.
    • 매 순간 객체를 생성하지 않고 객체를 재사용하는 간단한 방법은 객체 선언을 사용하는 것이다(싱글톤).
    • 캐시를 사용하는 팩토리 함수로도 객체를 재사용할 수 있다.
    • 팩토리 함수는 항상 같은 객체를 리턴하게 만들 수 있다. 값이 한번 계산되면 값을 즉시 구해올 수 있다. 하지만 캐시를 위한 Map을 저장해야 하므로 더 많은 메모리를 사용한다.
    • 캐시는 언제나 메모리와 성능의 트레이드 오프가 발생하므로, 캐시를 잘 설계하는 것은 쉽지 않다.

    지연 초기화

    • 무거운 클래스를 만들 때는 지연되게 만드는 것이 좋을 때가 있다. 예를 들어 A클래스에 B, C, D라는 무거운 인스턴스가 필요하다고 가성해보면 한번에 모두 생성하면 A 객체를 생성하는 과정이 굉장히 무거워질 것이다. 내부의 인스턴스를 지연 초기화 하면 A객체를 생성하는 과정을 가볍게 만들 수 있다.
    • 하지만 단점도 있다. 무거운 객체를 가졌지만 메서드의 호출은 빨라야할 수 있다. 따라서 지연 초기화는 상황에 맞게 사용해야한다.

    함수타입 파라미터를 갖는 함수에 inline 한정자를 붙여라

    • 코틀린 표준 라이브러리의 고차 함수를 살펴보면 대부분 inline 한정자가 붙어 있는 것을 볼 수 있다. inline한정의 역할은 컴파일 시점에 ‘함수를 호출하는 부분’을 ‘함수의 본문’으로 대체하는 것이다.
    • inline 한정자를 사용했을 때 장점
      • 타입 아규먼트에 reified 한정자를 붙여서 사용할 수 있다.
      • 함수 타입 파라미터를 가진 함수가 훨씬 빠르게 동작한다.
      • 비지역 리턴을 사용할 수 있다.
    • 모든 함수는 inline 한정자를 붙이면 조금 더 빠르게 동작한다. 함수 호출과 리턴을 위해 점프하는 과정과 백스택을 추적하는 과정이 없기 때문이다.
    • 하지만 함수 파라티터를 가지지 않는 함수에서는 이러한 차이가 큰 성능차이를 발생시키지 않는다. 그래서 간단한 함수에 inline 한정자를 붙이면 인텔리제이가 경고를 표시한다.
      • inline 한정자는 굉장히 유용한 한정자이지만 모든 곳에 사용할 수가 없다. 재귀적으로 사용하면 무한하게 대체되는 문제가 발생한다. 이러한 문제는 인텔리제이가 오류로 잡지 못하므로 굉장히 위험하다.

    인라인 클래스의 사용을 고려하라

    • 인라인으로 만들 수 있는 것은 함수뿐만이 아니다. 하ㅔ나의 값을 보유하는 객체도 inline으로 만들 수 있다. 기본 생성자 프로퍼티가 하나인 클래스 앞에 inline을 붙이면 해당 객체를 사용하는 위치가 모두 해당 프로퍼티로 교체된다.
    • 인라인 클래스를 사용하면 성능적인 오버헤드 없이 타입을 래핑할 수 있다.
    • 인라인 클래스는 타입 시스템을 통해 실수로 코드를 잘못 작성하는 것을 막아주므로, 코드의 안정성을 향상시켜준다. 의미가 명확하지 않은 타입, 특히 여러 측정 단위들을 함께 사용하는경우에는 인라인 클래스를 활용하는게 좋다.

    더이상 사용하지 않는 객체의 레퍼런스를 제거하라

    • 메모리 관리를 자동으로 해주는 프로그래밍 언어에 익숙한 개발자는 객체 해제를 따로 생각하지 않는다. 그렇다고 메모리 관리를 완전히 무시해버리면 메모리 누수가 발생해서 상황에 따라 OutOfMemoryError 가 발생하기도 한다.
    • 따라서 ‘더 이상 사용하지 않는 객체의 레퍼런스를 유지하면 안된다’ 라는 규칙 정도는 지켜주는 것이 좋다.
    • Activity를 여러 곳에서 자유롭게 접근하기 위해 companion 프로퍼티에 이를 할당해 두는 경우가 있는데 가비지 컬렉터가 해당 객체에 대한 메모리 해제를 할 수 없어서 굉장히 큰 메모리 누수가 발생하게 된다.
    • initialize를 null로 설정하기만 하면 가비지 컬렉터가 이를 처리할 수 있다. 하지만 거의 사용되지 않는 객체까지 이런 것을 신경 쓰는 것은 오히려 좋지 않을 수도 있다.
    • 쓸데없는 최적화가 모든 악의 근원이라는 말도 있지만 오브젝트에 null을 설정하는 것은 그렇게 어려운 일이 아니므로 무조건 하는 것이 좋다.
    • 특히 많은 변수를 캡처할 수 있는 함수 타입, Any 타입 또는 제네릭 타입과 같이 미지의 클래스일 때는 이러한 처리가 중요하다.
    • 사실 객체를 수동으로 해제해야 하는 경우는 굉장히 드물다. 일반적으로 스코프를 벗어나면서 어떤 객체를 가리키는 레퍼런스가 제거될 때 객체가 자동으로 제거된다.
    • 따라서 메모리 문제를 피하는 가장 좋은 방법은 변수를 지역 스코프에 정의하고, companion에 큰 데이터를 저장하지 않는 것이다.
    반응형

    댓글