Blog

이펙티브 코틀린(8)

이펙티브 코틀린(8)

8-1. 하나 이상의 처리 단계를 가진 경우에는 시퀀스를 사용하라

IterableSequence 는 완전히 다른 목적으로 설계되어서, 완전히 다른 형태로 동작한다
Sequence 는 지연(Lazy) 처리한다
시퀀스 처리 함수들을 사용하면, 데코레이터 패턴으로 꾸며진 새로운 시퀀스가 반환된다
최종 계산은 toList 또는 count 등의 최종 연산이 이루어질 때 수행된다
Iterable 은 처리 함수를 사용할 때 마다 연산이 바로 이루어지면서 List 를 반환한다
예시 코드
Sequence 의 장점
자연스러운 처리 순서 유지
최소한의 연산
무한 시퀀스 형태로 사용 가능
각 단계에서 컬렉션을 생성하지 않음

순서의 중요성

Sequence 처리는 요소당 하나씩 지정한 연산을 모두 적용한다. ( 이를 element-by-element order, lazy order 라고 부른다 )
Iterable 은 요소 전체를 대상으로 연산을 적용한다. ( 이를 step-by-step order, eager order 라고 부른다 )
예시 코드

최소 연산

Iterable 은 중간 연산이라는 개념이 없기 때문에, 전체 연산을 다 끝낸 후 요소의 개수를 가져와야 한다
Sequence 는 중간 연산이라는 개념이 있기 때문에 원하는 요소만 가져와서 원하는 처리를 할 수 있다
예시 코드
중간 처리 단계를 모든 요소에 적용할 필요가 없는 경우에는 Sequence 를 사용하는 것이 좋다

무한 시퀀스

Sequence 는 최종 연산이 일어나기 전까지는 컬렉션에 어떠한 처리도 하지 않는다
무한 시퀀스 ( Infinite Sequence ) 를 만들고, 필요한 부분까지만 값을 추출하는 것이 가능하다
무한 시퀀스를 만드는 방법은 generateSequence 또는 sequence 를 사용하면 된다
무한 시퀀스는 값을 몇 개 활용할지 결정해야 하며, 결정하지 않은 경우에는 무한 반복을 하게 된다
예시 코드

각각의 단계에서 컬렉션을 만들어내지 않음

컬렉션 처리 함수는 각각의 단계에서 컬렉션을 만들어진 결과를 활용하거나 저장할 수 있다는 장점이 있지만, 공간을 차지한다는 비용이 든다는 단점이 있다
예시 코드
일반적으로 파일을 처리할 때 Sequence 를 활용한다
Sequence 를 활용하면 메모리 절약 뿐만 아니라 성능도 향상시킬 수 있다

시퀀스가 빠르지 않은 경우

컬렉션 전체를 기반으로 처리해야 하는 연산은 Sequence 를 사용해도 빠르지 않다
e.g) stdlib 의 sorted 함수
sortedSequenceList 로 변환한 뒤 자바 stdlib 의 sort 를 사용해서 처리한다
이러한 변환 처리로 인해서 Sequence 가 Collection 처리보다 느려진다
무한 시퀀스에서는 sorted 를 사용하면 무한 반복에 걸릴 수 있기 때문에 조심해야 한다
일반적인 경우에서는 Collection 보다 Sequence 가 더 빠르다

자바 스트림의 경우

자바8 부터는 Collection 처리를 위해 Stream 기능이 추가되었다
코틀린의 Sequence 와 비슷한 형태로 동작한다
Stream 도 Lazy 하게 동작하며, 마지막 처리 단계에서 연산이 일어난다
Sequence 와 차이점
1.
코틀린의 Sequence 가 더 많은 처리 함수를 가지고 있다
a.
확장 함수를 사용해서 정의하기 때문이다
b.
사용하기 쉽다
2.
자바 Stream 은 병렬 함수를 사용해서 병렬 모드로 실행할 수 있다
a.
멀티 코어 환경에서 굉장히 큰 성능 향상을 가져온다
3.
코틀린의 Sequence 는 코틀린/JVM, 코틀린/JS, 코틀린/네이티브 등의 일반적인 모듈에서 모두 사용할 수 있다
a.
자바 Stream 은 코틀린/JVM 에서만 동작한다.
일반적으로 병렬 모드를 사용하지 않는다면, 코틀린 Sequence 를 사용하는게 좋다

코틀린 시퀀스의 디버깅

Kotlin Sequence Debugger 를 사용하면 어떻게 동작하는지 UI 로 확인할 수 있다

정리

Collection 과 Sequence 는 같은 처리 메서드를 지원하며, 사용하는 형태가 거의 비슷하다
일반적으로 데이터를 컬렉션에 저장하므로, Sequence 처리를 하려면 Sequence 로 변환하는 과정이 필요하다
Sequence 는 lazy 하게 처리된다
무거운 객체나 규모가 큰 Collection 을 여러 단계에 걸쳐서 처리할 대는 Sequence 를 사용하는 것이 좋다

8-2. 컬렉션 처리 단계 수를 제한하라

모든 Collection 처리 메서드는 비용이 많이 들어가는 작업이다
따라서 적절한 메서드를 활용해서, 컬렉션 처리 단계 수를 적절하게 제한하는 것이 좋다
예시 코드

정리

대부분의 컬렉션 처리 단계는 전체 컬렉션에 대한 반복중간 컬렉션 생성 이라는 비용이 발생한다
이 비용은 적절한 컬렉션 처리 함수들을 활용해서 줄일 수 있다

8-3. 성능이 중요한 부분에는 기본 자료형 배열을 사용하라

코틀린은 기본 자료형 (Primitive) 을 선언할 수 없지만, 최적화를 위해서 내부적으로는 사용할 수 있다
기본 자료형의 특징
가볍다 ( 일반적인 객체와 다르게 추가적으로 포함된 기능들이 없기 때문이다 )
빠르다 ( 값에 접근할 때 추가 비용이 들지 않는다 )
대규모의 데이터를 처리할 때 기본 자료형을 사용하면 상당히 큰 최적화를 할 수 있다
코틀린 타입
자바 타입
Int
Int
List<Int>
List<Integer>
Array<Int>
Integer[]
IntArray
int[]
기본 자료형 배열의 차이
IntArray vs List<Int>
메모리
단순 할당 되는 영역만 생각해보면 IntArray 는 400,000,016 Bytes , List<Int> 는 2,000,006,944 Bytes 를 할당한다
5배 정도의 차이가 발생한다
성능
1,000,000개의 숫자를 갖는 컬렉션을 사용해서 평균을 구하는 처리를 해보면 약 25% 정도 성능 IntArray 가 더 빠르다
기본 자료형을 폼하나는 배열은 코드 성능이 중요한 부분을 최적화할 때 활용하면 좋다
하지만, 일반적인 경우에는 List 를 사용하는것이 더 좋다
훨씬 더 기능이 다양하고, 더 많은 곳에서 쉽게 사용할 수 있기 때문이다

정리

일반적으로 Array 보다 List, Set 을 사용하는게 더 좋다
기본 자료형의 컬렉션을 굉장히 많이 가지고 있는 상황에서 성능을 높이고 메모리 사용량을 줄이려고 할 경우에는 Array 를 사용하는것이 좋다

8-4. mutable 컬렉션 사용을 고려하라

immutable collection 보다 mutable collection 이 성능적으로 더 빠르다
immutable collection 에서는 요소를 추가하려면 collection 을 복제해서 처리해야 하기 때문이다
구현 코드
mutable collection 이 성능적 관점에서 더 좋지만, immutable collection 이 안정성 측면에서 더 좋다
일반적인 지역 변수는 동기화와 캡슐화 문제가 없기 때문에, 지역 변수로 사용할 때는 mutable collection 을 사용하는 것이 더 합리적이다

정리

mutable collection 은 일반적으로 추가 처리가 빠르다
immutable collection 은 collection의 변경과 관련된 처리를 더 세부적으로 조정할 수 있다
지역 스코프에서는 세부적인 조정이 필요하지 않으므로, mutable collection 을 사용하는 것이 좋다
특히, utils 에서 요소 삽입이 자주 발생할 수 있기 때문이다