반응형

정규식은 문자열을 비교 처리할 때 사용되는 강력한 표현법이다.

Perl, JavaScript 등의 언어에서는 대단히 높은 활용도를 보여주고 있다.

 

C++ 언어의 경우 정규식을 처음부터 지원한 건 아니었고, C++11부터 지원하기 시작했다.

표준에 포함되긴 했지만, C++ 언어의 문법 구조상 정규식을 사용하는데는 불편함이 있다.

 

예컨데, 아래의 정규식은 메일 주소를 확인하기 위한 꽤 흔한 식이다.

var regExp = /^[0-9a-zA-Z]([-_\.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_\.]?[0-9a-zA-Z])*\.[a-zA-Z]{2,3}$/i;

 

하지만, C++에서 이런 정규식을 사용하려면 식을 문자열로 써야 하고, 이에 따라 \는 한 번이 아니라 두 번 적어야 한다.

마지막에 적힌 i(case-insensitive)는 별도의 옵션으로 전달해야 한다.

 

이러한 통상적인(?) 불편함 외에도 중대한 문제가 있다.

다름 아닌 멀티바이트 문자와 유니코드 문자를 구분해서 처리해야 된다는 점이다.

Perl, JS 등의 언어에서는 모든 문자를 유니코드로 처리하기 때문에 문자 집합에 대해 생각할 필요는 없다.

 

하지만, C++는 char와 wchar_t의 문자열을 따로 지원하고, 둘의 사용은 명확하게 구분되어 있다.

이에 따라 C++의 정규식에서는 regex와 wregex를 별도로 구현해두었다.

 

유니코드를 사용하기 위해 wchar_t를 사용한다고 모든 문제가 해결되는 것도 아니다.

Linux의 경우 wchar_t는 UTF-32이나 Windows 환경에서 wchar_t는 UTF-16이다.

UTF-16의 경우 BMP에 포함되는 문자까지는 2바이트로 저장되지만, 그 범위를 벗어나면 4바이트로 저장된다.

통상적인 경우에야 아무런 문제가 없지만, 정규식을 처리할 때에는 기대대로 동작되지 않을 수 있다.

 

이러한 문제 외에도 커다란 함정이 또 있는데, 다름 아닌 성능이다.

C++11에서 정식으로 도입된 정규식은 Visual C++ 뿐만 아니라 다른 컴파일러들에서도 하나같이 속도가 느리다.

속도 문제를 해결하기 위해선 boost::regex이나 google/re2 등의 다른 솔루션에 눈을 돌릴 수밖에 없다...

 


 

어떤 정규식 엔진을 사용하는 것이 좋을지를 알아보기 위해 몇 가지를 선정해서 돌려봤다.

 

1. RESearch

이 라이브러리는 scintilla에 포함된 라이브러리이다.

원본은 char 형에 대해서만 동작했는데, 유니코드 처리를 위해 wchar_t를 지원하도록 수정하여 적용했다.

다음의 정규식 엔진에 비해 지원되는 기능이 미미한 대신에 엄청난 속도를 자랑한다.

 

2. std::regex

C++에서 정식으로 지원하는 정규식 엔진이다.

 

3. boost::regex

그 유명한 boost의 정규식 엔진이다.

 

4. google/re2

구글에서 공개한 정규식 엔진이다.

1..3번이 wchar_t를 사용하는 것에 비해 google/re2는 char만 사용하고, 유니코드를 위해 UTF-8을 적용할 수 있다.

 

그리고, 이번에도 간단한 프로그램을 하나 만들어서 돌렸다.

 

정규식 뭐 그까이꺼 대~충... (아래쪽은 다른 라이브러리 테스트용)

 


 

1. 속도

위 네 가지 정규식 엔진 중 속도의 갑은 RESearch였다.

속도 측정을 위해 구라 제거기를 모사했다. 즉, 구라 제거기에서 사용되는 모든 정규식을 돌려서 시간을 측정했다.

예상대로 std::regex가 가장 느리고, boost::regex와 google/re2가 비슷하지만 근소하게 google/re2가 빨랐다.

하지만, RESearch가 이들을 가볍게 상회하는 성능을 보여줬다.

 

2. 문자 처리 능력

RESearch는 성능은 빠르지만, 그룹 연산자를 잘 처리하지 못한다.

아니, 그 전에 수량한정자도 처리하지 못한다.

 

즉, 아래와 같은 식을 이용해서

((?:\d{2})(?:\:\d{2}){2}).*?\[\s(\d+).*?\[\s(\d+)

 

아래와 같은 로그의 일부를 돌리는 작업에선 전혀 써먹을 수가 없다.

Line 394 : [21:44:07 Oct 12 Fri] @0x3924004E|JVM| Free Memory: Heap [ 2368340/ 6291456], Native[ 7299824/32505856]

 

이 외에도 수량자 * 를 조금만 복잡하게 사용해도 잘못된 정규식으로 판단하는 문제도 있다.

 

즉, 범용으로 사용하기엔 무리가 있고, 제한된 상황에서 최대한 빠른 정규식을 사용할 때[각주:1]만 사용할 수 있다.

 

영어는 넘어가고[...] 한글을 잘 인식하는지 확인해봤다.

다음의 두 식으로

[ㄱ-ㅣ가-힣]{3}
\p{Hangul}{3}

 

아래와 같은 간단한 문자열이 일치하는지 확인해봤다.

가나다

 

[ㄱ-ㅣ가-힣]{3}의 경우 RESearch를 제외한 모든 엔진이 잘 동작했으나, 두 번째 식은 google/re2에서만 잘 처리했다.

 

마지막 테스트는 엽기적인 이모지 테스트.

식 자체는 더할 나위 없이 단순하다.

[😀-😯]{3}

 

위의 식으로 아래의 문자열(?)이 일치하는지 확인해봤다.

😀😊😯

 

예상한대로 google/re2만 제대로 처리했다.

저 이모지들의 코드가 BMP를 넘어서는 즉, UTF-16에서 네 바이트로 저장되는 문자들이라 처리하지 못한 것.

 

이건 저 라이브러리들이 wchar_t가 UTF-16이라는 점을 인식하지 못한다는 방증이다.

 


 

결론: google/re2 쓰자. 이게 최선임.

 

덧1. google/re2는 UTF-8밖에 처리하지 못하므로 wchar_t(UTF-16)을 UTF-8로 변환하는 기능도 구현했음

       이 변환 시간을 포함시켜도 boost::regex보다 높은 속도를 보여줌

덧2. google/re2 쪽이 다른 라이브러리들을 적용하는 것에 비해 대략 200kB 정도 실행파일이 큼

덧3. std::regex는 시험 과정에서 종종 다운되는 현상이 있었는데, 원인은 결국 찾지 못함

 

 

  1. 그래서 구라 제거기에서 이 엔진을 사용했는데, 다른 엔진으로 바꾸기 위해 이러한 시험을 진행한 것임 [본문으로]
반응형

공유하기

facebook twitter kakaoTalk kakaostory naver band