SimpleIsBest.NET

유경상의 닷넷 블로그

문자열 이야기 (6) - Equals 와 == 연산자

by 블로그쥔장 | 작성일자: 8/17/2005 4:29:00 PM
이 글은 오래된 전에 작성된 글입니다. 따라서 최신 버전의 기술에 알맞지 않거나 오류를 유발할 수 있습니다. 저자는 이 글에 대한 질문을 받지 않을 것입니다. 하지만 이 글이 리뉴얼 되면 이 글에 대한 질문을 하거나 토론을 할 수도 있습니다.
지난 문자열 이야기 (4) : 문자열 비교 에서 문자열 비교에 대해 간단히 이야기한 == 연산자와 Equals 메쏘드에 대해 좀 더 이야기 해볼려구 합니다. 가끔 문자열 비교를 할 때 == 가 더 빠르다던가 Equals 가 더 빠르다던가 하는 글들을 접할 수 있는데, 정말 어느 것이 얼마나 빠른지 살펴보려고 합니다. 김이 새시겠지만, 결론부터 말하면 차이가 거의 없다 입니다. 왜 그런지 이제 살펴 보시지요...

Equals vs. == Operator

닷넷 프로그래밍에 관련된 커뮤니티 사이트들을 돌아다니거나 블로그를 접하다 보면 팁 성격의 글로써 문자열 비교에 대한 이야기를 접하게 된다. 그 중 문자열을 비교할 때, str == "" 보다는 str.Length == 0 을 쓰라든가 등등의 좋은 정보가 있는가 하면 그렇지 않은 내용도 있다. 오늘 이야기 할 내용은 C#에서 문자열 비교 시 Equals가 더 좋은지 == 연산자가 더 좋은지를 살펴보도록 하겠다. 혹자는 Equals가 == 연산자에 비해 훨씬 더 빠르니 이것을 사용하라고 하는데 이것은 근거가 없는 유언비어 이며, 이런 주장을 하는 사람이 보인다면 가까운 경찰서나 군부대에 신고하기 바란다. 왜 그런고 이제 까발려 보자.

Equals() Method

Equals 메쏘드는 System.String 클래스의 인스턴스 메쏘드로서 두 문자열의 내용을 비교 한다. 지난 문자열 이야기에서 문자열 비교와 참조 비교의 차이점에 대해 이야기 했으니 이에 대한 배경이 없다면 먼저 읽어 보기 바란다. String.Equals 메쏘드의 구현을 Reflector로 까보면 다음과 같이 선언되어 있다.

[MethodImpl(MethodImplOptions.InternalCall)]
public extern bool Equals(string value);

위 선언은 Equals 메쏘드가 CLR 내부에 구현된 Unmanaged 코드이며 Equals 메쏘드가 호출되면 C 수준의 문자열 비교(strcmp 정도?)가 수행된다는 말이다. InternalCall 메쏘드는 P/Invoke 와는 수준이 다르며 Interop이 발생하지 않음에 유의하자.

== Operator

이제 == 연산자를 살펴보자.  문자열 비교에 == 연산자가 사용되면 C# 컴파일러는 op_Equality 메쏘드를 호출하도록 코드를 생성한다. op_Equality 메쏘드의 구현을 Reflector로 살펴보면 다음과 같다.

public static bool operator ==(string a, string b)
{
      
return string.Equals(a, b);
}

아무런 작업 없이 System.String 클래스의 스태틱 메쏘드인 Equals(string, string) 메쏘드를 호출하는 것을 알 수 있다. 이와 같이 간단한 메쏘드는 런타임에 인라인(inline)되므로 성능상에 아무런 효과도 미치지 못한다. 실제 부하는 스태틱 메쏘드인 Equals(string, string)에서 발생하는 것이다. Equals(string, string) 스태틱 메쏘드도 Reflector로 까보면 다음과 같다.

public static bool Equals(string a, string b)
{
      
if ((object)a == (object)b) {
            
return true;
      
}
      
if ((a != null) && (b != null)) {
            
return a.Equals(b);
      
}
      
return false;
}

이 메쏘드 역시 매우 간단한 구현이다. 먼저, 두 문자열의 참조값 자체가 같은가 비교한다. 두 문자열의 참조 값이 같다면 문자열은 당연히 동일할 것이다(문자열 비교와 참조 비교의 차이점에 대해서는 지난 필자의 포스트를 참고하기 바란다). 만약 두 문자열이 같은 참조값을 갖는 경우, 문자열 비교를 수행하지 않으므로 효율적이라 할 수 있겠다. 반면 String.Equals 인스턴스 메쏘드 호출은 무조건 문자열 비교를 수행함에 유의하자.

필자주)
String.Equals(string, string) 스태틱 메쏘드를 Reflector로 살펴보면 첫 번째 비교 코드가 다음과 같이 나타난다.

      if (a == b) {
            
return true;
      
}

이 비교는 다시 op_Equals()를 호출하게 되는 것 아닌가? 그렇지 않다. Reflector는 IL 코드를 최대한 소스코드와 비슷하게 디컴파일(decompile) 해주는데, 단순한 참조 비교(포인터 값의 비교)와 문자열 비교를 모두 == 로 표시해 버린다. 이 때문에 위 코드 처럼 보이는데, 실제 IL 코드를 살펴보면 IL 명령어 bne (비교 후 같지 않으면 분기) 를 사용하여 단순한 참조 비교를 수행함에 유의하자.

두 문자열 참조가 다르다면 이제 실제 문자열 비교를 위해 String.Equals(string) 인스턴스 메쏘드를 호출하게 된다. 이 메쏘드는 이미 앞서 설명한 바로 그 Equals 메쏘드이다. 결론적으로 == 연산자(op_Equality 메쏘드) String.Equals(string) 메쏘드를 통해 실질적인 문자열 비교를 한다는 것이다.

Comparison

Equals 메쏘드와 == 연산자를 이제 비교해 보자. 어떤 것을 사용하든 문자열 비교는 Equals 메쏘드에 의해 수행된다. == 연산자를 사용하면 2개의 메쏘드가 더 호출되지만 그 중 1개는 인라인(inline) 되며, 나머지 1개 역시 간단한 포인터 비교 만이 수행되므로 성능에 미치는 영향은 대단히 미미하다는 것이다. 실제로 필자가 Equals 메쏘드와 == 연산자의 성능을 프로파일러로 비교해 보면 둘의 성능차이는 대단히 미미했다. 필자의 1.86GHz 노트북 컴퓨터에서 두 문자열 비교 방법은 20 ~ 80 나노초(nanosecond; 십억 분의 1초) 정도의 차이를 보였다. 더우기 == 연산자는 코드의 가독성을 높여 주므로 이 정도의 성능차이를 커버하는 장점이 될 것이다. (반면 == 연산자의 모호성에도 주의 하자)

그렇다면 왜 Equals 보다 == 가 느리다고 생각하는 사람들이 있을까? 그것은 잘못된 테스트에 기인하는 경우가 많다. Equals와 == 문자열 성능 비교에 사용된 다음 코드를 살펴보자

static void Main(string[] args)
{
    
string strCompared "the string";
    
    if 
(strCompared.Equals("the string"))
        Console.WriteLine(
"True");

    if 
(strCompared == "the string")
        Console.WriteLine(
"True");

}

위 코드를 프로파일러로 돌려보면 == 연산자가 수백 배 느린 것처럼 나타난다. 그럼 필자가 구라를 친 것일까? 아니다... -_- 위 코드는 JIT 컴파일을 고려하지 않은 테스트 코드이다. Equals 메쏘드는 앞서 언급한 대로 InternalCall 을 수행하는 메쏘드이다. 즉, JIT 컴파일을 수행하지 않는 메쏘드이다. 반면 == 연산자는 op_Equality 메쏘드 호출이 필요하고, op_Equality 메쏘드를 호출하기 위해서는 JIT 컴파일이 최초 1회 수행 되어야 한다. 위 코드는 Main 메쏘드, 즉 프로그램 엔트리 포인트에서 최초로 == 연산자를 사용함으로써 op_Equality 메쏘드와 이 메쏘드가 호출하는 Equals(string, string) 스태틱 메쏘드를 JIT 컴파일 하는 효과가 나온다. 그래서 약 7 밀리초(천분의 7초) 정도가 소요되어 버린다. 따라서 위 코드로 Equals와 == 의 성능을 비교하는 것은 마치 내가 어제 나이트에 갔어. 나이트에 가서 부킹을 했는데, 여자가 왔어. 여자한테 '놀러 오셨어요?' 하고 물으니, 아니요 '목욕하러 왔어요'』하는 것과 같은 이야기인 것이다.

정확한 테스트를 위해서는 JIT 컴파일과 같은 효과를 배제한 후에 테스트를 수행해야 한다. 필자가 프로파일러에 사용한 코드는 다음과 같다.

static void Main(string[] args)
{
    
string strCompared "the string";

   
// 워밍업...
    Console.WriteLine(strCompared.Equals("the string"));
    Console.WriteLine(strCompared == "the string"));     
// 여기서 op_Equality() 메쏘드의 JIT 컴파일이 수행된다.

    if 
(strCompared.Equals("the string"))  // 약 0.48 microsecond 정도 소요된다.
        Console.WriteLine(
"True");

    if 
(strCompared == "the string")       // 약 0.51 microsecond 정도 소요된다. (JIT 컴파일은 없다)
        Console.WriteLine(
"True");

}

VB.NET Code

VB.NET에서 상황은 약간 다르다. Equals 호출은 C#이나 VB.NET이나 동일하므로 생각할 필요가 없으나, = 연산자(VB.NET에서는 == 이 아닌 = 임에 유의하자)는 op_Equality 메쏘드가 아닌 Microsoft.VisualBasic.CompilerServices.StringType 클래스의 StrCmp 메쏘드가 호출된다. 이 메쏘드는 다시 String.CompareOrdinal을 호출한다. CompareOrdinal 메쏘드는 현재 쓰레드의 언어를 고려하지 않고, 문자열의 각 문자를 정수화하여 비교를 수행한다. 즉, Equals와 효과가 같다. 하지만 호출 경로가 약간 차이가 날 뿐이다. 성능 차이 역시 그다지 크지 않다.

Conclusion

결론은 Equals 나 == 이나 성능차이는 미미하다는 것이다. 문자열 참조가 같은 두 변수에 대해서는 == 의 성능이 더 좋으며, 문자열 참조가 같지 않은 경우에는 Equals가 미세하게나마 낫다. VB.NET의 = 연산자는 C#의 == 이나 Equals 메쏘드보다 약간 더 느리긴 하다. 정말 빡시게 따져보면, 나노초(0.000000001 초. 10억분의 1초) 단위의 결과가 나오는 == 연산자와 Equals 메쏘드는 경우에 따라 ==가 Equals 보다 몇 배 빠르거나 Equals가 == 보다 수십 퍼센트 더 빠를 수도 있다.

하지만 천만 분의 1초, 혹은 일억 분의 1초를 가지고 이것이 낫냐 저것이 낫냐를 따지는 것 보다는 코드의 가독성이나 로직 구현의 정확성에 좀 더 신경 쓰는 것이 낫다는 것이 필자의 주장이다. CPU와 메모리는 점차로 빨라지고 있다. 20여년전 4MHz 8비트 CPU와 64KB 메모리를 보유한 컴퓨터에서야 보다 빠른 코드를 고민하는 것은 생산적인 고민 이였겠지만, 눈부시게 빠른 CPU와 커다란 캐시를 가진 현재 하드웨어 상황에서 수억 분의 1초를 가지고 고민할 필요는 없지 않을까? 이런 관점에서 보았을 때, Equals를 쓰던 == 을 쓰건 코드를 보다 쉽게 읽을 수 있다면, 이 정도의 성능차이는 상관 없지 않을까?

Equals 가 == 보다 빠르다고 말하는 사람이나 그것이 틀렸다고 지적하는 필자나... 난 왜 블로그를 쓰나 몰라... 바부 아냐? -_-



Comments (read-only)
#re: 문자열 이야기 (6) - Equals 와 == 연산자 / 남처리 / 10/30/2008 12:59:00 AM
쥔장님 말씀대로 다른곳에서는 equals가 대부분 빠르다고 써있어서 그냥 차이가 나는구나 하고는 euqals 함수를 사용했었는데.
이제는 굳이 그럴 필요가 없겠군요 ^^
좋은글 감사히 잘 읽었습니다.