Kotlin Sequences and Lazy Evaluation

694 단어·2 분·원문(.md)

Overview #

While reading "Kotlin in Action," I saw how sequences can improve operation performance. This made me think they could be useful in backend server development or when solving algorithmic problems, so I'm studying them to better grasp the concept.

Lazy Evaluation #

Lazy evaluation is a method that defers computation until a value is needed. In this approach, computation is performed only when the value is required, and not before. This helps in efficient use of computing resources.

If all computations were performed in advance, some of them might never actually be used. Therefore, performing only necessary computations can save resources.

Lazy evaluation is typically used in functional programming, where programming focuses on functions that generate values rather than the values themselves. In this context, values can be generated by lazily evaluating the return values of functions.

For example, in Kotlin, you can use Sequence or the lazy keyword to lazily initialize variables. This allows variable initialization to be performed later, thereby saving resources.

Sequence #

Kotlin provides various APIs for handling collections, and Sequence is one of them. Sequences are used to process a series of values. Unlike regular collections, sequences generate values, i.e., perform lazy evaluation, each time an intermediate operation is executed.

Advantages of Sequences

  • Handles lazy evaluation.
  • Optimizes intermediate operations. (e.g., when performing a map operation after a filter operation, a sequence can execute both operations at once.)
  • Memory saving (Sequences generate only one element at a time, so they use less memory compared to collections.)
  • Operation chaining (Sequences allow multiple operations to be chained together on a single line, making the code more concise.)
  • Functional programming style (Sequences align with the functional programming style, minimizing side effects and improving code readability.)
  • Thread safety (Sequences are thread-safe, meaning they can be accessed and used concurrently by multiple threads, which is useful in multi-threaded environments.)

Sequences can be created using the sequenceOf() function. Additionally, other collections can be converted into sequences using the asSequence() function.

Sequences typically provide intermediate operations such as map(), filter(), flatMap(), and terminal operations such as toList(), toSet(), toMap().

Measuring Sequence Performance #

val list = (1..1000000).toList()

// 컬렉션 사용
val result1 = list.filter { it % 2 == 0 }.map { it * it }.toList()

// 시퀀스 사용
val result2 = list.asSequence().filter { it % 2 == 0 }.map { it * it }.toList()

val time1 = measureTimeMillis {
    list.filter { it % 2 == 0 }.map { it * it }.toList()
}

val time2 = measureTimeMillis {
    list.asSequence().filter { it % 2 == 0 }.map { it * it }.toList()
}

println("컬렉션 사용 ${time1}ms")
println("시퀀스 사용 ${time2}ms")

The code above is an example of measuring time when processing 1,000,000 elements, performing a map() operation after a filter() operation. It converts the list to a sequence using asSequence(), performs the operations, and then converts the result back to a list. The results are as follows:

컬렉션 사용 20ms
시퀀스 사용 15ms

In this example, using sequences shows slightly faster performance than using collections, but this may vary depending on the execution environment. Therefore, when using sequences, it is advisable to measure and compare performance in each specific situation.

Situations to avoid using sequences? #

  1. Operations on small datasets (Lazy evaluation can introduce additional overhead.)
  2. When intermediate results are needed (Lazy evaluation makes it difficult to see intermediate results, complicating debugging and testing.)
  3. When parallel processing is required (Sequences are processed on a single thread. Therefore, using sequences is inefficient when parallel processing is needed.)
    • While sequences guarantee thread safety, interactions with other threads that occur during sequence processing do not guarantee thread safety.
    • Therefore, when using sequences in a multi-threaded environment, sufficient consideration for thread safety and appropriate synchronization should be performed.
    • Even in multi-threaded environments, thread safety and synchronization issues must be considered. While safety is guaranteed, appropriate synchronization should be performed when necessary.
Back-End/kotlin/sequence.md