728x90
장점
1. 병렬 처리 및 동시성에 유리
함수형 프로그래밍에서는 불변성과 순수 함수 덕분에 병렬 처리가 안전하며 동시성 문제를 줄일 수 있습니다.
Java 예제
import java.util.Arrays;
import java.util.List;
public class FunctionalExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
// Stream API를 사용하여 병렬 처리
int sum = numbers.parallelStream()
.map(x -> x * 2) // 각 요소를 2배로 만듦
.reduce(0, Integer::sum); // 모든 요소를 합산
System.out.println("Sum: " + sum); // 출력: 72
}
}
Kotlin 예제
fun main() {
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8)
// 병렬 처리를 위해 map과 reduce를 활용
val sum = numbers
.map { it * 2 } // 각 요소를 2배로
.reduce { acc, value -> acc + value } // 합산
println("Sum: $sum") // 출력: 72
}
장점:
병렬 처리 시 불변성 덕분에 데이터 충돌 문제가 발생하지 않으며, 동시성을 안전하게 처리할 수 있습니다.
2. 지연 평가(Lazy Evaluation)
필요할 때만 계산을 수행하여 불필요한 연산을 줄입니다.
Java 예제
import java.util.stream.Stream;
public class LazyEvaluationExample {
public static void main(String[] args) {
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5)
.filter(x -> {
System.out.println("Filtering: " + x);
return x > 2;
})
.map(x -> {
System.out.println("Mapping: " + x);
return x * 2;
});
// 최종 연산 수행 (지연 평가로 인해 여기서 계산 시작)
stream.forEach(System.out::println);
}
}
출력:
Filtering: 1
Filtering: 2
Filtering: 3
Mapping: 3
6
Filtering: 4
Mapping: 4
8
Filtering: 5
Mapping: 5
10
Kotlin 예제
fun main() {
val numbers = listOf(1, 2, 3, 4, 5)
val result = numbers
.asSequence() // 지연 평가 시작
.filter {
println("Filtering: $it")
it > 2
}
.map {
println("Mapping: $it")
it * 2
}
result.forEach { println(it) }
}
출력:
Filtering: 1
Filtering: 2
Filtering: 3
Mapping: 3
6
Filtering: 4
Mapping: 4
8
Filtering: 5
Mapping: 5
10
장점:
filter와 map은 필요할 때만 수행되어, 연산량을 줄이고 성능을 최적화합니다.
3. 캐싱 및 메모이제이션(Memoization)에 적합
순수 함수의 특성상, 동일 입력에 대해 동일 출력을 보장하므로, 결과를 캐싱하여 성능을 개선할 수 있습니다.
Kotlin 예제
fun fibonacci(): (Int) -> Long {
val cache = mutableMapOf<Int, Long>()
return { n ->
cache.getOrPut(n) {
if (n <= 1) 1L else fibonacci()(n - 1) + fibonacci()(n - 2)
}
}
}
fun main() {
val fib = fibonacci()
println(fib(10)) // 출력: 89
println(fib(15)) // 출력: 987
}
단점
1. 높은 메모리 소비
불변 데이터 구조는 데이터를 복사하여 새로 생성하므로, 메모리 사용량이 증가할 수 있습니다.
Kotlin 예제
fun main() {
val numbers = listOf(1, 2, 3)
val newNumbers = numbers + 4 // 기존 리스트 복사 후 새로운 리스트 생성
println("Original: $numbers") // [1, 2, 3]
println("New: $newNumbers") // [1, 2, 3, 4]
}
- 단점: 기존 데이터를 유지하면서 새 데이터를 생성하기 때문에, 메모리 사용량이 증가합니다.
2. 재귀 호출로 인한 스택 오버플로우
함수형 프로그래밍에서 재귀를 자주 사용하지만, 꼬리 재귀 최적화(Tail Call Optimization)가 없는 환경에서는 스택 오버플로우 문제가 발생할 수 있습니다.
Java 예제
public class RecursiveExample {
public static void main(String[] args) {
System.out.println(factorial(10000)); // 스택 오버플로우 발생
}
public static int factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
}
Kotlin 예제 (꼬리 재귀 최적화)
Kotlin은 tailrec 키워드를 사용하여 꼬리 재귀 최적화를 지원합니다.
tailrec fun factorial(n: Int, acc: Long = 1): Long {
return if (n <= 1) acc else factorial(n - 1, acc * n)
}
fun main() {
println(factorial(10000)) // 꼬리 재귀 최적화로 스택 오버플로우 없음
}
단점: 꼬리 재귀 최적화가 없는 경우, 반복문이 재귀보다 성능이 더 좋습니다.
3. 불필요한 중간 데이터 생성
함수형 체이닝(map, filter)에서 각 단계가 중간 데이터를 생성하면 성능이 저하될 수 있습니다.
Java 예제
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class IntermediateData {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 각 단계에서 중간 데이터가 생성됨
List<Integer> result = numbers.stream()
.map(x -> x * 2)
.filter(x -> x > 5)
.collect(Collectors.toList());
System.out.println(result); // [6, 8, 10]
}
}
단점:
중간 데이터(map -> filter -> collect)가 계속 생성되어, 메모리와 CPU 사용량이 증가합니다.
해결: Lazy Evaluation을 지원하는 스트림 활용.
결론
- 장점: 병렬 처리, 지연 평가, 캐싱 등으로 성능 최적화 가능.
- 단점: 메모리 사용 증가, 재귀 호출 문제, 중간 데이터 생성으로 인한 성능 저하 가능.
언어와 상황에 맞게 함수형 접근 방식을 최적화하여 사용하는 것이 중요합니다.
728x90
'BackEnd' 카테고리의 다른 글
함수 호출 오버헤드가 발생 이란? (0) | 2025.01.19 |
---|---|
명령형과 선언형 차이[이론] (0) | 2025.01.19 |
함수형 프로그래밍이란? (0) | 2025.01.19 |
헥사고날 아키텍처(Hexagonal Architecture)_ 추가 수정 필요 (0) | 2024.08.27 |
멀티 스레드 환경에 대한 이해 (0) | 2024.02.05 |