본문 바로가기
C#/Effective C#

[Effective C# Item 37] 쿼리를 사용할 때는 즉시 평가보다 지연 평가가 낫다

by 코모's 2023. 5. 2.
반응형

쿼리를 정의한다고 해서 결과 데이터나 시퀀스를 즉각적으로 얻어오는 것이 아니다. 실제로는 쿼리를 정의하는 작업은 수행 시에 어떤 과정으로 작업을 수행할지에 대한 절차만을 정의한 것에 지나지 않는다.

지연 평가(lazy evaluation) : 실제로 쿼리의 결과를 이용하여 순회를 수행할 때 결과가 생성.

즉시 평가(eager evaluation) : 일반 변수를 사용하는 것처럼 즉각적으로 그 값을 얻어오는 것.

 

 

지연 평가는 어떻게 동작하는가?

아래 코드는 현재 시간을 생성하는 시퀀스를 작성하고 그 시퀀스를 세계 공용 포맷으로 다시 변경하는 코드이다.

var sequence1 = Generate(10, () => DateTime.Now); // 오전 8:00 
Thread.Sleep(60000); // 1분간 대기 
var sequence2 = from value in sequence1 select value.ToUniversalTime(); // 오전 8:01

sequence2는 sequence1이 생성한 일련의 데이터 세트를 가져와서 세계 공용 포맷으로 변경된 시퀀스를 생성하는 것이 아니라, 세계 공용 포맷의 시간 정보를 필요한 시점에 완전히 새로운 값을 생성한다. 따라서 위 코드는 오전 8:01에 대한 세계 공용 포맷 시퀀스를 결과값으로 내놓을 것이다.

 

전체 시퀀스가 준비되어야 하는 일부 쿼리 표현식

일부 쿼리 표현식의 경우 결과를 얻기 위해서 반드시 전체 시퀀스가 준비되어야 하는 경우가 있다.

static void Main(string[] args) 
{
	var answers = from number in AllNumbers() select number; 
    var smallNumbers = answers.Take(10); 
    foreach (var num in smallNumbers) 
    Console.WriteLine(num); 
} 

static IEnumerable<int> AllNumbers() 
{
	var number = 0; 
    while (number < int.MaxValue) 
    {
    	yield return number++; 
    }
}

위 코드가 양의 모든 정수 시퀀스를 생성하는 AllNumbers()가 있음에도 빠르게 동작할 수 있는 이유는 사전에 전체 시퀀스를 생성하는 것이 아니기 때문이다. 위 코드는 Take()메서드를 사용해서 처음부터 10개의 객체를 반환하는 작업을 수행한다. 하지만, 다음과 같이 쿼리를 변경하면 프로그램은 상당한 시간이 소요된다.

static void Main(string[] args) 
{
	var answers = from number in AllNumbers() where number < 10 select number; 
    var smallNumbers = answers.Take(10); 
    
    foreach (var num in smallNumbers) 
    Console.WriteLine(num); 
 }

 

이 코드가 많은 시간을 소요하는 이유는 where 코드가 시퀀스 내의 모든 값을 대상으로 비교 연산을 수행해야 하기 때문이다. 즉, 이 코드가 이전 코드와 동일한 작업을 수행하지만 전체 시퀀스가 필요한 예다.

이 밖에도 orderby, Max, Min 같은 경우도 전체 시퀀스가 필요하다.

 

이와 같은 전체 시퀀스가 필요한 메서드를 사용할 때는 몇 가지 유의할 사항이 있다.

 

1. 시퀀스가 무한정 지속될 가능성이 있다면 이 같은 메서드를 사용할 수 없다.

2. 시퀀스가 무한이 아니더라도 시퀀스를 필터링하는 쿼리 메서드는 다른 쿼리보다 먼저 수행하는 것이 좋다. 컬렉션의 요소를 필터링하여 그 개수를 줄일 수 있다면 이후에 수행될 쿼리의 성능을 개선할 수 있기 때문이다.

 

결론

거의 대부분의 경우에 지연 평가를 사용하면 즉시 평가에 비해서 작업의 양도 줄고 유연성도 증가한다.

가끔 즉시 평가가 필요한 드문 경우에는 ToList()나 ToArray() 메서드를 사용하면 된다. 하지만 즉시 평가가 반드시 필요한 경우가 아니라면 대체로 지연 평가를 사용하도록 하자.

 

 
반응형