현대 프로그래밍 언어는 대부분 컬렉션 처리를 잘 지원합니다. 컬렉션 처리를 최적화하는 것은 생각보다 어렵지만 굉장히 중요합니다. 그러니 몇 가지 규칙을 기억하고, 상황에 맞게 생각해야합니다.
아이템 49 - 하나 이상의 처리 단계를 가진 경우에는 시퀀스를 사용하라
아래의 Iterable과 Sequence는 완전히 다른 목적으로 설계되어서, 다른 형태로 동작합니다.
interface Iterable<out T> {
operator fun iterator(): Iterator<T>
}
interface Sequence<out T> {
operator fun iterator(): Iterator<T>
}
무엇보다 Sequence는 지연 처리됩니다. 따라서 시퀀스의 최종적인 계산은 최종 연산이 이루어질 때 수행됩니다. 반면에 Iterable은 처리 함수를 사용할 때마다 연산이 이루어집니다.
따라서 시퀀스를 사용하면 다음과 같은 장점이 있습니다.
- 자연스러운 처리 순서를 유지합니다.
- 최소한만 연산합니다.
- 무한 시퀀스 형태로 사용할 수 있습니다.
- 각각의 단계에서 컬렉션을 만들어 내지 않습니다.
순서의 중요성
이터러블과 시퀀스 처리는 순서가 달라지면 결과도 달라집니다. 시퀀스 처리는 요소 하나하나에 지정한 연산을 한꺼번에 적용합니다. 반면에 이터러블은 요소 전체를 대상으로 연산을 차근차근 적용해 나갑니다.
sequenceOf(1, 2, 3)
.filter { print("F$it, "); it % 2 == 1 }
.map { print("M$it, "); it * 2 }
.forEach { print("E$it, ") }
// F1, M1, E2, F2, F3, M3, E6,
listOf(1, 2, 3)
.filter { print("F$it, "); it % 2 == 1 }
.map { print("M$it, "); it * 2 }
.forEach { print("E$it, ") }
// F1, F2, F3, M1, M3, E2, E6,
우리가 고전적인 반복문과 조건문을 활용해서 구현한다면, 이는 시퀀스로 처리할때와 동일합니다.
최소 연산
이터러블은 만약 리스트 요소 10개에 대해서만 연산이 필요한 경우에도 전체 리스트에 대한 연산을 수행해야합니다. 그런 다음 요소 10개에 대한 연산 결과를 얻습니다.
하지만, 시퀀스는 중간 연산이라는 개념을 갖고 있으므로, 앞의 요소 10개에만 원하는 처리를 적용할 수 있습니다.
무한 시퀀스
시퀀스는 최종 연산이 일어나기 전까지는 컬렉션에 어떠한 처리도 하지 않습니다. 따라서 무한 시퀀스를 만들고, 필요한 부분까지만 값을 추출하는 것도 가능합니다.
무한 시퀀스는 아래와 같이 만들 수 있습니다.
generateSequence(1) { it + 1 }
.map { it * 2 }
.take(10)
.forEach { print("$it, ") }
// 2, 4, 6, 8, 10, 12, 14, 16, 18, 20,
각각의 단계에서 컬렉션을 만들어 내지 않음
표준 컬렉션 처리 함수는 각각의 단계에서 새로운 컬렉션을 만들어 냅니다. 그렇기 때문에 이는 비용이 듭니다. 크기가 큰 요소를 처리할수록 비용은 커집니다.
처리 단계가 많아지고, 컬렉션이 커지면 컬렉션 처리보다는 시퀀스 처리를 사용하는 것이 좋습니다.
시퀀스가 빠르지 않은 경우
컬렉션 전체를 대상으로 처리해야하는 연산의 경우에는 시퀀스를 사용한다고 해도 큰 의미가 없습니다.
코틀린에서는 `sorted`가 존재합니다. 시퀀스에 sorted를 사용하면 List로 변환한 뒤에 처리를 하게 됩니다. 그래서 추가적인 비용이 발생하게 됩니다.
무한 시퀀스에 sorted를 적용하면 무한 루프에 빠지게 되기도 합니다.
자바 스트림의 경우
자바 8부터는 스트림 기능이 추가되었습니다. 이는 시퀀스와 비슷한 형태로 동작합니다.
productList.asSequence()
.filter { it.bought }
.map { it.price }
.average()
productList.stream()
.filter { it.bought }
.mapToDouble { it.price }
.average()
.orElse(0.0)
아이템 50 - 컬렉션 처리 단계 수를 제한하라
모든 컬렉션 처리 메서드는 비용이 많이 들기에 처리 단계 수를 적절하게 제한하는 것이 좋습니다. 그렇기에 다양한 컬렉션 처리 함수를 알아두면 좋습니다.
아이템 51 - 성능이 중요한 부분에는 기본 자료형 배열을 사용하라
코틀린읜 기본 자료형을 선언할 수 없지만, 최적화를 위해서 내부적으로는 사용할 수 있습니다.
대규모의 데이터를 처리할 때 기본 자료형을 사용하면, 상당히 큰 최적화가 이루어집니다. 그런데 코틀린의 List와 Set 등의 컬렉션은 제네릭 타입으로 기본 자료형을 사용할 수 없기에 랩핑된 타입을 사용해야 합니다.
아이템 52 - mutable 컬렉션 사용을 고려하라
immutable 컬렉션보다 mutable 컬렉션이 좋은 점은 성능적인 측면에서 더 빠르다는 것입니다.
immutable한 컬렉션에 요소를 추가하려면, 비용이 굉장히 많이 듭니다. 그렇기에 일반적인 지역 변수로 사용될 경우에는 mutable 컬렉션을 사용하는 것이 더 합리적이라고 할 수 있습니다.
'스터디 > 이펙티브 코틀린' 카테고리의 다른 글
[이펙티브 코틀린] 7장 - 비용 줄이기 (0) | 2025.04.28 |
---|---|
[이펙티브 코틀린] 6장 - 클래스 설계 (0) | 2025.04.07 |
[이펙티브 코틀린] 5장 - 객체 생성 (0) | 2025.02.04 |
[이펙티브 코틀린] 4장 - 추상화 설계 (0) | 2025.01.21 |
[이펙티브 코틀린] 3장 - 재사용성 (0) | 2025.01.13 |