시배's Android

Effective Kotlin | 7장. 비용 줄이기 본문

Book/Effective Kotlin

Effective Kotlin | 7장. 비용 줄이기

si8ae 2024. 3. 20. 22:38

아이템45. 불필요한 객체 생성을 피하라

  • Int 와 같은 기본형을 사용하면, 일반적으로 기본 자료형 int 로 컴파일 된다.
  • 하지만 nullable 로 만들거나, 타입 아규먼트로 사용할 경우에는 Integer 로 컴파일 된다.
  • 기본 자료형은 null 일 수 없고, 타입 아규먼트로도 사용할 수 없기 때문이다.
  • 객체 생성 비용은 항상 클까?
  • 현대 64비트 JDK 에서 객체는 8바이트의 배수만큼 공간을 차지한다.
  • 앞부분 12바이트는 헤더로서 반드시 있어야 하므로, 최소 크기는 16바이트이다.
  • 추가로 객체에 대한 레퍼런스도 공간을 차지한다.
  • 일반적으로 레퍼런스는 -Xmx32G 까지는 32/64비트 플랫폼 모두 4바이트다.
  • 64비트 플랫폼에서는 32G(-Xmx32G)부터는 8바이트이다.
  • 캐시를 활용하는 팩토리 함수
  • 객체 생성이 무겁거나, 동시에 여러 mutable 객체를 사용해야 하는 경우에는 객체 풀을 사용하는 것이 좋다.
  • WeakReference
  • 가비지 컬렉터가 값을 정리(clean)하는 것을 막지 않는다.
  • 따라서 다른 레퍼런스(변수)가 이를 사용하지 않으면 곧바로 제거된다.
  • SoftReference
  • 가비지 컬렉터가 값을 정리할 수도 있고, 정리하지 않을 수도 있다.
  • 일반적인 JVM 구현의 경우, 메모리가 부족해서 추가로 필요한 경우에만 정리한다.
  • 따라서 캐시를 만들 때는 SoftReference 를 사용하는 것이 좋다.
  • 프로퍼티를 지연되게 만드는 것은 무거운 클래스를 사용할 때 유용하다.
  • 기본 자료형을 랩(wrap)한 자료형이 사용되는 경우
  • nullable 타입을 연산할 때(기본 자료형은 null 일 수 없으므로)
  • 타입을 제네릭으로 사용할 때
  • 성능이 중요한 경우라면 함수 안에서 nullable 타입보다는 기본 자료형을 사용하자.

아이템46. 함수 타입 파라미터를 갖는 함수에 inline 한정자를 붙여라

  • inline 한정자의 역할은 컴파일 시점에 '함수를 호출하는 부분'을 '함수의 본문'으로 대체하는 것이다.
  • 일반적인 함수를 호출하면 함수 본문으로 점프하고, 본문의 모든 문장을 호출한 뒤에 함수를 호출했던 위치로 다시 점프하는 과정을 거친다.
  • inline 한정자를 사용하면 좋은점
    • 타입 아규먼트에 reified 한정자를 붙여서 사용할 수 있다.
    • 함수 타입 파라미터를 가진 함수가 훨씬 빠르게 동작한다.
      • 함수 호출과 리턴을 위해 점프하는 과정과 백스탭을 추적하는 과정이 없기 때문이다.
    • 비지역(non-local)리턴을 사용할 수 있다.
  • public 인라인 함수 내부에서는 private 과 internal 가시성을 가진 함수와 프로퍼티를 사용할 수 없다.
  • 인라인 함수는 구현을 숨길 수 없으므로, 클래스에 거의 사용되지 않는다.
  • crossinline
    • 아규먼트로 인라인 함수를 받지만, 비지역적 리턴을 하는 함수는 받을 수 없게 만든다.
    • 인라인으로 만들지 않은 다른 람다 표현식과 조합해서 사용할 때 문제가 발생하는 경우 활용
  • noinline
    • 아규먼트로 인라인 함수를 받을 수 없게 만든다.
    • 인라인 함수가 아닌 함수를 아규먼트로 사용하고 싶을 때 활용한다.
  • 인라인 함수가 사용되는 주요 사례
    • print 함수처럼 매우 많이 사용되는 경우
    • filterIsInstance 함수처럼 타입 아규먼트로 reified 타입을 전달받는 경우
    • 함수 타입 파라미터를 갖는 톱레벨 함수를 정의해야 하는 경우
      • 특히 컬렉션 처리 함수와 같은 헬퍼 함수(map, filter, flatMap, joinToString 등)
      • 스코프 함수(also, apply, let 등)
      • 톱레벨 유틸리티 함수(repeat, run, with)
  • API 를 정의할 때 인라인 함수를 사용하는 경우는 거의 없다.
  • 또한 한 인라인 함수가 다른 인라인 함수를 호출하는 경우, 코드가 기하급수적으로 많아질 수 있으므로 주의해야 한다.

아이템47. 인라인 클래스의 사용을 고려하라

  • 인라인 클래스를 사용하면 해당 객체를 사용하는 위치가 모두 해당 프로퍼티로 교체된다.
  • inline 클래스의 메서드는 모두 정적 메서드로 만들어진다.
  • 인라인 클래스는 다른 자료형을 래핑해서 새로운 자료형을 만들 때 많이 사용된다.
    • 이 때 어떠한 오버헤드도 발생하지 않는다.
  • inline 클래스는 다음과 같은 상황에서 많이 사용된다.
    • 측정 단위를 표현할 때
    • 타입 오용으로 발생하는 문제를 막을 때
  • 인라인 클래스도 다른 클래스와 마찬가지로 인터페이스를 구현할 수 있지만, 인터페이스를 통해서 타입을 나타내려면, 객체를 래핑해서 사용해야 하기 때문에
    인터페이스를 구현하는 인라인 클래스는 아무런 의미가 없다.
  • 의미가 명확하지 않은 타입, 특히 여러 측정 단위들을 함께 사용하는 경우에는 인라인 클래스를 활용해보자.

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

  • 객체에 대한 참조를 companion 으로 유지해 버리면, 가비지 컬렉터가 해당 객체에 대한 메모리 헤제를 할 수 없다.
  • 상태를 유지할 때는 메모리 관리를 염두에 두어야 한다.
  • 톱레벨 프로퍼티 또는 객체 선언(companion 객체 포함)으로 큰 데이터를 저장하지 말자.