더 이상 설정 파일을 매번 변경하지 말자!”

 웹 및 윈도우 응용프로그램을 개발할 때 응용프로그램의 각종 설정 정보를 저장하기 위해 설정 파일을 사용합니다. 이 설정 파일은 데이터베이스 연결 문자열, 디버그 설정, 인증, 권한, 파일 서버의 경로와 같은 응용프로그램에 필요한 설정 정보를 저장하게 됩니다. 간단히 나열하긴 했지만 실제 환경에서의 설정 파일은 상당히 복잡하게 구성되어있는 경우가 허다합니다. 여러분이 지금 개발중인 응용프로그램이 아주 간단한 테스트용 프로그램이 아니라 실 운영을 목적으로 하는 프로그램이라면 최소한 로컬 PC와 운영 서버가 필요하게 됩니다. 로컬 PC와 운영 서버의 환경이 100% 동일할 수 없기 때문에 설정 파일 또한 서로 다를 확률이 아주 높습니다. 단 두 대의 컴퓨터라면 수동으로 설정 파일을 변경할 수도 있겠지만 개발 및 운영환경이 로컬 PC, 개발 서버, 테스트 서버, 스테이징 서버, 운영 서버로 구성되어 있다면 수동으로 설정 파일을 관리하는 것은 스트레스 그 자체일 것입니다.

 

이를 해결하기 위해 ASP.NET 4.0에서는 설정 파일 변환기능(Configuration Transformation)을 제공하고 있습니다. 사실 설정 파일 변환기능은 ASP.NET 4.0의 기능이라기 보다는 Visual Studio 2010의 기능입니다. 이것이 뭐가 중요하냐 라고 생각할 수 도 있지만 Visual Studio 2010의 기능이기 할 수 있는 일이 더 많아진다는여기까지 궁금증을 유발 시키고, 이 글의 중반부에서 다시 설명 드리겠습니다.^^

 

Web.confg 변환하기

우선 웹 프로젝트에서 설정파일을 변환하는 기능에 대해 알아보도록 하겠습니다. 다음은 일반적으로 볼 수 있는 Web.config 파일입니다.

 

Web.config

<?xml version="1.0"?>

<configuration>

  <connectionStrings>

    <add name="DatabaseConnection" connectionString="ConnectionString"/>

  </connectionStrings>

</configuration>

 

아래에 있는 두 설정 파일은 진짜 설정파일이 아니라 Web.config 파일이 배포될 때 어떻게 변해야 할지를 지정하는 XML 파일에 불과합니다. 구문을 간단히 살펴보면 변환 설정은 XML-Document-Transform 네임스페이스에 정의된 XML 특성을 사용하여 지정하게 되며 이 네임스페이스는 xdt Prefix에 매핑 됩니다. 이어서 나오는 connectionString 요소는 Web.config와 동일하지만 Transform 어트리뷰트와 Locator 어트리뷰트가 더 있는 것을 볼 수 있습니다.

 

Locator는 자신에 할당된 값에 해당하는 요소를 선택하며 Transform Locator가 선택한 요소에 대해 수행할 작업을 지정하게 됩니다. 좀더 자세히 살펴보면 xdt:Locator=”Match(name)” Web.config 파일에서 name 어트리뷰트가 “DatabaseConnection”인 요소를 찾습니다. 그 다음 xdt:Transform SetAttributes를 통해 변환 파일에 설정되어 있는 모든 어트리뷰트를 Locator가 선택한 요소에 적용하게 되는 것입니다. SetAttributes에는 매개변수를 지정할 수 있는데 콤마(,)를 구분 자로 하여 어트리뷰트 이름을 전달할 수 있습니다. “SetAttributes(connectionString)”처럼 작성하게 되면 지정된 어트리뷰트에 해당하는 값만을 업데이트 하게 됩니다.

 

다음은 위의 구문을 사용하여 생성한 설정 변환 파일입니다.

 

Web.Debug.config

<?xml version="1.0"?>

<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">

    <connectionStrings>

        <add name="DatabaseConnection"

          connectionString="DebugConnectionString"

          xdt:Transform="SetAttributes" xdt:Locator="Match(name)"/>

    </connectionStrings>

</configuration>

 

Web.config와 동일하나 connectionString의 값이 DebugConnectionString입니다.

 

Web.Release.config

<?xml version="1.0"?>

<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">

    <connectionStrings>

        <add name="DatabaseConnection"

          connectionString="ReleaseConnectionString"

          xdt:Transform="SetAttributes" xdt:Locator="Match(name)"/>

    </connectionStrings>

</configuration>

 

Web.config와 동일하나 connectionString의 값이 ReleaseConnectionString입니다.

 

설정파일 변환에 대한 자세한 정보는 http://msdn.microsoft.com/ko-kr/library/dd465326.aspx를 참고 하세요.

 

변환 파일 생성이 완료되었으므로 배포를 통해 파일이 변환되는지 확인해 보도록 하겠습니다. 프로젝트의 컨텍스트 메뉴에서 [Publish] 메뉴를 선택하여 웹 게시를 수행합니다. 웹 게시 대화상자에서 게시 방식을 File System으로 설정하고 적절한 폴더를 선택한 다음 게시를 수행합니다. File System으로 설정하는 이유는 그냥 테스트가 쉽도록 하기 위함입니다.

 

 

Figure 1 One-Click 배포 대화상자

 

One-Click 배포에 관한 자세한 정보는 http://msdn.microsoft.com/ko-kr/library/dd465337.aspx를 참고 하세요.

 

이상 없이 배포가 완료되었으면 배포 경로에 Web.config 파일이 있는 것을 볼 수 있으며 이 파일을 편집기를 통해 확인해 보면 connectionString ReleasConnectionString으로 변경된 것을 확인할 수 있습니다. 솔루션 구성(Solution Configuration) Debug로 변경한 다음 다시 One-Click 배포를 수행하면 connectionString의 값이 DebugConnectionString로 변경되는 것을 확인할 수 있습니다.

 

설정 파일 변환에 의해 생성된 Web.config

<?xml version="1.0"?>

<configuration>

  <connectionStrings>

    <add name="DatabaseConnection" connectionString="ReleaseConnectionString"/>

  </connectionStrings>

</configuration>

 

설정파일 변환에 대해서 알아 봤으니 이제 우리의 실 개발환경에서 어떻게 활용하면 좋을까에 대해 고민해 보겠습니다. 지금 진행중인 프로젝트의 서버 구성이 로컬 pc, 개발서버, 스테이징 서버, 운영서버로 구성되어 있다고 가정하면, 각 서버에서 사용하는 DB 연결 문자열과 파일 서버의 경로가 모두 다를 경우 각 서버에 배포할 때마다 해당되는 값들을 수작업으로 변경해야 합니다. 이런 불편하고 비 합리적인 작업을 해결하기 위해 설정 파일 변환이 나온 것이며 이를 실제로 적용해 보도록 하겠습니다.

 

먼저 솔루션 구성(Solution Configuration)을 추가합니다. [Build] -> [Configuration Manager]를 선택하면 Configuration Manager 대화상자가 표시되며 여기서 Active solution configuration 콤보 박스의 <New…>항목을 선택합니다.

 

Figure 2 새로운 솔루션 구성 생성

 

그러면 New Solution Configuration 대화상자가 표시되며 Name 텍스트 박스에 개발서버를 나타내는 Development를 입력하고 설정을 복사해올 대상을 Debug로 선택합니다. 이렇게 하는 이유는 기본 설정을 Debug에서 상속 받겠다는 것이고 추가적인 사항만 변경하겠다는 의미입니다. 또한 개발 서버는 운영환경이 아닌 개발환경이므로 Debug의 설정 내용을 복사하는 것입니다.

 

Figure 3 새 솔루션 구성 대화상자

 

<New…>를 선택하여 스테이징 서버를 의미하는 Staging과 실 운영 서버를 의미하는 Operation을 추가합니다. 이 두 설정은 운영환경이므로 Release로부터 설정 정보를 복사하도록 합니다.

 

설정 추가가 완료되면 Web.config 파일에서 마우스 오른쪽 버튼을 클릭하여 [Add Config Transform]이라는 메뉴를 선택합니다. 이 메뉴는 솔루션 구성(Solution Configuration)에 추가된 모든 항목에 대해 변환파일을 생성해 줍니다.

 

Figure 4 설정파일 변환 자동 생성

 

변환 파일이 모두 생성되면 솔루션 탐색기의 Web.config는 다음처럼 구성됩니다. 간단하고 멋지지 않습니까? 마지막으로 추가된 변환 파일에 DB 연결 문자열과 파일 서버의 경로를 변경해 주고 저장합니다.

 

Figure 5 생성된 변환 파일들

 

이제 모든 준비가 완료되었습니다. 오늘은 스테이징 서버에 배포를 하는 날입니다. 솔루션 구성(Solution Configuration) Staging로 변경하고 One-Click 배포를 수행하기만 하면 추가 작업 없이 설정 파일이 변경되고 배포가 완료됩니다.

 

Figure 6 솔루션 구성 선택

 

App.config 변환하기

일반 응용프로그램(Console, WinForm, WPF )도 설정 파일이 변환이 가능할까? 기본적으로는 안 됩니다. 안 되는데 뭣 하러 이 글을 쓰는 것일까? 기본적으로 안 되는 것이지 불가능한 것은 아닙니다. 기본적으로 안되지만 불가능하지 않다는 것은 아무래도 웹 프로젝트에서 보다 조금 번거로운 작업이 있다는 것을 의미합니다. 그래도 많이 어렵거나 번거롭지는 않습니다. 매번 설정 파일을 변경하는 것 보다는

 

그렇다면 어떻게 가능한 것일까? 서두에서 꺼내다 만 이야기가 있습니다. 설정 파일 변환은ASP.NET 4.0이 제공하는 것이 아니라 Visual Studio 2010이 제공하는 기능이라고. 고로 Visual Studio 2010을 사용하기만 하면 어떤 설정파일이던 모두 변환할 수 있다는 말입니다.

 

여기서는 콘솔 응용프로그램을 사용합니다. WinForm, WPF 모두 동일합니다. 파일 변환 기능은 프로젝트 파일이 어떻게 구성되어 있느냐에 따라 결정됩니다. 일단 아래의 코드를 <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> 설정 이후에 추가 합니다.

 

설정 파일 변환을 가능하게 하기 위한 XML

<UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll" />

<Target Name="AfterCompile" Condition="exists('app.$(Configuration).config')">

<TransformXml Source="app.config" Destination="$(IntermediateOutputPath)$(TargetFileName).config" Transform="app.$(Configuration).config" />

<ItemGroup>

    <AppConfigWithTargetPath Remove="app.config" />

    <AppConfigWithTargetPath Include="$(IntermediateOutputPath)$(TargetFileName).config">

    <TargetPath>$(TargetFileName).config</TargetPath>

    </AppConfigWithTargetPath>

</ItemGroup>

</Target>

 

빌드 시 Microsoft.Web.Publishing.Tasks.dll 어셈블리를 사용하여 파일 변환을 수행합니다.  app.config을 소스로 하고 app.솔루션구성.config 파일 즉, app.debug.config 같은 파일을 변환 파일로 사용하여 변환을 하게 됩니다.

 

프로젝트 파일은 프로젝트 언로드(Unload)이후에 편집할 수 있습니다.

 

프로젝트 파일을 변경하였으면 추가 적인 솔루션 구성(Solution Configuration)을 추가 합니다. 여기서는 test 설정 하나만 더 추가 하였습니다. 그 다음 변환 파일을 추가하기 위해 app.config에서 마우스 오른쪽 버튼을 클릭하여 [Add Config Transform]를 선택하려고 하면 [Add Config Transform] 메뉴가 없다는 것을 알게 될 것입니다. 아쉽게도 이 기능은 Web 프로젝트에서만 제공되는 것 같습니다. 자동으로 안되면 어떻게 할까요? 그냥 수동으로 추가해 버리면 그만입니다.

 

수동으로 app.debug.config, app.release.config, app.test.config를 추가 합니다. 자 이제 빌드를 위한 모든 준비가 완료 되었습니다. 솔루션 구성(Solution Configuration) test로 하고 빌드를 수행한 다음 출력 폴더의 ConsoleEx.exe.config(콘솔 프로젝트의 실행 파일 명이 ConsoleEx.exe)을 확인해 보면 설정 파일 변환이 정상적으로 동작 했음을 볼 수 있습니다.

 

번 외로 진정한 개발자라면 깔끔한 코드와, 들여쓰기, 단 하나의 경고도 무시할 수 없는 깐깐함에 더해 멋진 솔루션 구성도 생각해 볼 수 있습니다. 앞에서 만든 프로젝트 구조를 살펴보면 다음처럼 설정파일이 웹 프로젝트와는 다르게 모두 나열되어 있는 것을 볼 수 있습니다.

 

Figure 7 정리되지 않은 설정 파일들

 

설정 파일을 변환하는 데는 아무런 문제도 되지 않지만 성격상!! 이를 고쳐 볼까 합니다. 간단합니다. 다시 한번 프로젝트 파일을 언로드(Unload)하고 파일을 열어보면 설정파일이 있는 ItemGroup이 다음처럼 구성된 것을 볼 수 있습니다.

 

변경 전 app.config

<ItemGroup>

    <None Include="app.config" />

    <None Include="app.debug.config"/>

    <None Include="app.release.config"/>

    <None Include="app.test.config"/>

</ItemGroup>

 

여기서 ItemGroup의 각 하위 요소에 다음 코드처럼<DependentUpon>App.config</DependentUpon>와 같은 자식 요소를 추가하면 Web 프로젝트의 설정 파일처럼 깔끔하게 하위 노드로 들어가게 됩니다. DependentUpon 요소는 지정된 요소에 의존하는 구조로 구성된다는 의미 입니다.

 

변경 후 app.config

<ItemGroup>

    <None Include="app.config" />

    <None Include="app.debug.config">

        <DependentUpon>App.config</DependentUpon>

    </None>

    <None Include="app.release.config">

        <DependentUpon>App.config</DependentUpon>

    </None>

    <None Include="app.test.config">

        <DependentUpon>App.config</DependentUpon>

    </None>

</ItemGroup>

 

Figure 8 깔끔하게 정리된 설정 파일들

 

그다지 중요하지 않아 보일지는 몰라도 이런 습관을 들이는 건 썩 훌륭한 습관이지 않나 하는 생각을 해 봅니다. 아니면 말고..

지금까지 닷넷 응용프로그램에서 설정 파일을 변환하는 방법에 대해 알아 봤습니다. 최초 한번의 고생으로 배포 때 마다 행복할 수 있다면 상당히 훌륭하고 편리한 기능이라고 생각됩니다. 귀찮아 말고 적용해 보면 생각 보다 좋다는 것을 느낄 수 있을 것입니다.

 

더 이상 매번 설정 파일을 변경하지 말자!

 

 

저작자 표시
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by kyeongkyun(kobukii) kyeongkyun

Part1, 2에서 정규표현식을 어떻게 사용하는지에 대해 알아봤습니다. 이 글에서는 Regex 클래스를 사용하는 다양한 방법들에 대해 성능테스트를 해 보고 어떻게 하면 최적의 성능으로 정규표현식을 사용할 수 있는지에 대해 알아보도록 하겠습니다.

 

Regex 클래스와 정규표현식 패턴을 사용하여 개발을 해 본 개발자들은 수행성능에 대해 불평을 하곤 합니다. 왜 이렇게 느린 거야? 라고 말입니다. 하지만 왜 성능이 좋지 않은지 쉽게 진단할 수 없습니다. 정규표현식의 수행속도가 느린 이유는 크게 두 가지로 볼 수 있습니다. 첫 번째는 당연히 정규표현식 패턴 자체에 문자가 있는 경우며, 두 번째는 Regex 클래스를 사용하는 방식이 잘못 되었기 때문입니다. .NET 프레임워크의 정규표현식 개체 모델의 핵심은 Regex 클래스입니다. 다시 말해, Regex 엔진을 사용하는 방식이 정규표현식의 성능에 영향을 주는 핵심 요인인 것입니다.

 

정규표현식 패턴 자체에 대한 성능은 그 분량이 방대하여 이 글에서 다루지 않으며 정규표현식 관련 서적을 참고 하십시오.

 

인스턴스 메서드와 정적 메서드의 성능 차이

Regex 클래스의 인스턴스 메서드를 호출하고 싶다면, 반드시 Regex 클래스의 인스턴스를 생성해야 합니다. 이전 강좌에서 설명하였듯이 Regex 클래스의 인스턴스를 생성 할 때는 생성자 매개변수로 정규표현식 패턴을 전달해야 합니다. 하지만 Regex 개체가 한번 생성되면 생성자 매개변수에 지정된 정규표현식을 변경할 수 없습니다. 이는 강력하게 결합(tightly coupling)되어 있다고 말할 수 있으며, 새로운 정규표현식을 사용하기 위해서는 새로운 Regex 개체를 생성해야만 합니다.

 

이 문제를 해결하기 위해 Regex 클래스는 정적 메서드를 제공하고 있습니다. 아시다시피 정적 메서드는 인스턴스를 생성할 필요가 없으므로 불필요한 인스턴스의 생성으로 인한 비용을 줄일 수 있게 됩니다.

 

static void Run2(string pUrl)

{

   //비교 대상 입력 자료

    string[] urls = { 

                        "http://taeyo.net",

                        "http://www.taeyo.net",

                        "http://www.taeyo.net/board",

                        "http://taeyo.net/board/board.aspx",

                        "http://taeyo.net/talk/board.aspx?id=12",

                        "http://taeyo.net&",

                        "http://www.taeyo.net%",

                        "http://www.taeyo.net/board@",

                        "http://dayofdays.net",

                        "http://blog.dayofdays.net",

                        "http://blog.dayofdays.net/article",

                        "http://dayofdays.net/article/article.aspx?category=test",

                        "http://dayofdays.net/test-url"                        

                    };

   //인스턴스 메서드를 사용하는 테스트

    Stopwatch sw = Stopwatch.StartNew();

    foreach (string url in urls)

    {

        if (IsValidUrlWidthInstance(pUrl, url))

        {

            Console.WriteLine("{0} is valid", url);

        }

        else

        {

            Console.WriteLine("{0} is not valid", url);

        }

    }

    sw.Stop();

    Console.WriteLine("Instance Elapsed Time :\t{0}", sw.Elapsed);

 

   //정적 메서드를 사용하는 테스트

    sw = Stopwatch.StartNew();

    foreach (string url in urls)

    {

        if (IsValidUrlWidthStatic(pUrl, url))

        {

            Console.WriteLine("{0} is valid", url);

        }

        else

        {

            Console.WriteLine("{0} is not valid", url);

        }

    }

    sw.Stop();

    Console.WriteLine("Static Elapsed Time :\t{0}", sw.Elapsed);

}

 

//Regex클래스의 인스턴스 생성

static bool IsValidUrlWidthInstance(string pUrl, string url)

{

    Regex regex = new Regex(pUrl);

    return regex.IsMatch(url);

}

//Regex 클래스의 정적 메서드 호출

static bool IsValidUrlWidthStatic(string pUrl, string url)

{

    return Regex.IsMatch(url, pUrl);

}

 

static void Main(string[] args)

{

   //단순한 정규표현식

    string pUrl1 = @"^https?://([\w-]+\.)+[\w-]+(/[\w-./?&%=]*)?$";

   //복잡한 정규표현식

    string pUrl2 = @"^(http|https|ftp)\://([a-zA-Z0-9\.\-]+(\:[a-zA-Z0-9\.&amp;%\$\-]+)*@)?((25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])|([a-zA-Z0-9\-]+\.)*[a-zA-Z0-9\-]+\.[a-zA-Z]{2,4})(\:[0-9]+)?(/[^/][a-zA-Z0-9\.\,\?\'\\/\+&amp;%\$#\=~_\-@]*)*$";

    Console.WriteLine("1. 간단한 정규표현식---------------");

    Run2(pUrl1);

    Console.WriteLine("2. 복잡한 정규표현식---------------");

    Run2(pUrl2);

}

 

위 코드는 단순한 정규표현식과 복잡한 정규표현식을 각각 사용하는 인스턴스 생성 방식과 정적 메서드 호출 방식을 테스트 하기 위한 코드입니다. 실행결과는 다음과 같습니다.

 

1. 간단한 정규표현식---------------

http://taeyo.net is valid

http://www.taeyo.net is valid

http://www.taeyo.net/board is valid

http://taeyo.net/board/board.aspx is valid

http://taeyo.net/talk/board.aspx?id=12 is valid

http://taeyo.net& is not valid

http://www.taeyo.net% is not valid

http://www.taeyo.net/board@ is not valid

http://dayofdays.net is valid

http://blog.dayofdays.net is valid

http://blog.dayofdays.net/article is valid

http://dayofdays.net/article/article.aspx?category=test is valid

http://dayofdays.net/test-url is valid

Instance Elapsed Time : 00:00:00.0022522

http://taeyo.net is valid

http://www.taeyo.net is valid

http://www.taeyo.net/board is valid

http://taeyo.net/board/board.aspx is valid

http://taeyo.net/talk/board.aspx?id=12 is valid

http://taeyo.net& is not valid

http://www.taeyo.net% is not valid

http://www.taeyo.net/board@ is not valid

http://dayofdays.net is valid

http://blog.dayofdays.net is valid

http://blog.dayofdays.net/article is valid

http://dayofdays.net/article/article.aspx?category=test is valid

http://dayofdays.net/test-url is valid

Static Elapsed Time :   00:00:00.0029799

2. 복잡한 정규표현식---------------

http://taeyo.net is valid

http://www.taeyo.net is valid

http://www.taeyo.net/board is valid

http://taeyo.net/board/board.aspx is valid

http://taeyo.net/talk/board.aspx?id=12 is valid

http://taeyo.net& is not valid

http://www.taeyo.net% is not valid

http://www.taeyo.net/board@ is valid

http://dayofdays.net is valid

http://blog.dayofdays.net is valid

http://blog.dayofdays.net/article is valid

http://dayofdays.net/article/article.aspx?category=test is valid

http://dayofdays.net/test-url is valid

Instance Elapsed Time : 00:00:00.0206471

http://taeyo.net is valid

http://www.taeyo.net is valid

http://www.taeyo.net/board is valid

http://taeyo.net/board/board.aspx is valid

http://taeyo.net/talk/board.aspx?id=12 is valid

http://taeyo.net& is not valid

http://www.taeyo.net% is not valid

http://www.taeyo.net/board@ is valid

http://dayofdays.net is valid

http://blog.dayofdays.net is valid

http://blog.dayofdays.net/article is valid

http://dayofdays.net/article/article.aspx?category=test is valid

http://dayofdays.net/test-url is valid

Static Elapsed Time :   00:00:00.0101541

계속하려면 아무 키나 누르십시오 . . .

 

결과를 보면 단순한 패턴을 사용할 경우 인스턴스를 생성하는 방식과 정적 메서드를 사용하는 방식 중 인스턴스를 생성하는 방식이 0.0007초 더 빠르지만 그 차이는 미미한 것으로 보이며, 동일한 코드를 복잡한 패턴으로 사용할 경우 거의 0.01초 차이로 정적 메서드를 사용한 방식이 빠른 것을 확인할 수 있습니다.

 

이렇게 성능에서 차이가 나는 것은 복잡한 정규표현식 패턴은 opcode로 변경 되는데 더 많은 비용을 필요로 하지만, 정적 메서드 호출은 정규표현식 패턴을 캐시 하기 때문에 그 성능이 향상되는 것입니다. , 인스턴스 메서드의 호출은 매번 정규표현식을 opcode로 변경하는 반면, 정적 메서드 호출은 캐시 된 정규표현식과 다른 패턴이 지정된 경우에만 opcode로 변환하고 캐시에 존재할 경우 캐시에서 opcode를 가져와 사용하게 되는 것입니다.

 

오직 정적 메서드만 캐시가 된다는 것을 기억해 두세요. 캐시의 크기는 Regex.CacheSize 속성을 통해 변경 가능하며 기본 캐시의 크기는 15입니다. 만약 캐시의 크기를 초과하여 사용한다면 최근에 사용되지 않은 캐시를 새로운 정규표현식으로 대체하게 됩니다.

 

다섯 가지 정규표현식 생성 방식 및 성능

지금까지는 인스턴스 메서드와 정적 메서드에 초점을 맞추어 알아보았습니다. 이제 좀 더 성능 향상을 가져올 수 있는 컴파일 된 정규표현식에 대해 알아보도록 하겠습니다.

 

.NET 에서 정규표현식 엔진은 세가지 방식으로 정규표현식을 만들어 낼 수 있습니다.

 

1.      정규표현식 인터프리트 - Regex 개체를 생성하거나 Regex 클래스의 정적 메서드를 호출 할 때(정적 메서드에 지정된 정규표현식이 캐시에서 발견되지 않은 경우) 정규표현식은 opcode로 변환됩니다. 메서드가 호출되면 opcode MSIL로 변환되고 JIT 컴파일러에 의해 실행됩니다. 이 방식을 인터프리트 방식이라 하며 시작 시간이 빠른 반면 대신 실행시간이 길어집니다.

2.      정규표현식 컴파일 - Regex 개체를 생성하거나 Regex 클래스의 정적 메서드를 호출 할 때(정적 메서드에 지정된 정규표현식이 캐시에서 발견되지 않은 경우) RegexOptions.Compiled 옵션을 지정하면 정규표현식은 MSIL로 변환됩니다. 메서드가 호출되면 JIT 컴파일러에 의해 MSIL이 실행됩니다. 컴파일 된 정규표현식은 인스턴스 및 정적 메서드 호출에 모두 사용될 수 있습니다. 컴파일 된 정규표현식은 실행시간이 빠른 반면 시작 시간이 길어집니다.

3.      정규표현식을 별도의 어셈블리로 분리 여러 개의 정규표현식을 별도의 어셈블리에 별도의 클래스로 생성합니다. 어셈블리로부터 MSIL이 로드 되고 JIT 컴파일러에 의해 실행됩니다. 여러 클래스에서 참조를 통해 사용가능하며 시작 시 드는 비용을 런타임에서 디자인 타임으로 옮긴다는 장점이 있습니다. , 런타임 환경에서 시작시간과 실행시간 모두 빠르다는 장점이 있습니다.

 

세가지 방식은 시작시간과 실행시간의 장단점이 존재합니다. 이제 앞에서 설명한 모든 방식을 사용하여 성능을 측정해 보도록 하겠습니다. 다음 코드는 다섯 가지 방식의 성능 측정을 위한 테스트 코드입니다.

 

다섯 가지 방식

1.     인터프리트 방식의 인스턴스 메서드 사용

2.     컴파일 방식의 인스턴스 메서드 사용

3.     인터프리트 방식의 정적 메서드 사용

4.     컴파일 방식의 정적 메서드 사용

5.     별도의 어셈블리를 참조하여 인스턴스 메서드 사용

 

private static void PerformanceTest(string input)

{

    Stopwatch sw = null;

    string pUrl = @"https?://([\w-]+\.)+[\w-]+(/[\w-./?&%=]*)?";

 

    //[인스턴스 메서드인터프리트

    sw = Stopwatch.StartNew();

    Regex regex1 = new Regex(pUrl);

    MatchCollection matches1 = regex1.Matches(input);

    foreach (Match match in matches1)

    {

    }

    sw.Stop();

    Console.WriteLine("[Instance][Interpret] Matches Count={0}, Elapsed Time : {1}", matches1.Count, sw.Elapsed);

 

    //[인스턴스 메서드컴파일

    sw = Stopwatch.StartNew();

    Regex regex2 = new Regex(pUrl, RegexOptions.Compiled);

    MatchCollection matches2 = regex1.Matches(input);

    foreach (Match match in matches2)

    {

    }

    sw.Stop();

    Console.WriteLine("[Instance][Compile] Matches Count={0}, Elapsed Time : {1}", matches2.Count, sw.Elapsed);

 

    //[정적 메서드인터프리트

    sw = Stopwatch.StartNew();

    MatchCollection matches3 = Regex.Matches(input, pUrl);

    foreach (Match match in matches3)

    {

    }

    sw.Stop();

    Console.WriteLine("[Static][Interpret], Matches Count={0}, Elapsed Time : {1}", matches3.Count, sw.Elapsed);

 

    //[정적 메서드컴파일

    sw = Stopwatch.StartNew();

    MatchCollection matches4 = Regex.Matches(input, pUrl, RegexOptions.Compiled);

    foreach (Match match in matches4)

    {

    }

    sw.Stop();

    Console.WriteLine("[Static][Compile] Matches Count={0}, Elapsed Time : {1}", matches4.Count, sw.Elapsed);

 

    //[인스턴스 메서드어셈블리

    sw = Stopwatch.StartNew();

    URLRegex urlRegex1 = new URLRegex();

    MatchCollection matches5 = urlRegex1.Matches(input);

    foreach (Match match in matches5)

    {

    }

    sw.Stop();

    Console.WriteLine("[Instance][Assembly] Matches Count={0}, Elapsed Time : {1}", matches5.Count, sw.Elapsed);

 

}

 

다음은 코드를 실행한 결과 입니다.

 

1. 소량 데이터를 사용한 검사---------------

[Instance][Interpret] Matches Count=1, Elapsed Time : 00:00:00.0000213

[Instance][Compile] Matches Count=1, Elapsed Time : 00:00:00.0000086

[Static][Interpret], Matches Count=1, Elapsed Time : 00:00:00.0000139

[Static][Compile] Matches Count=1, Elapsed Time : 00:00:00.0000094

[Instance][Assembly] Matches Count=1, Elapsed Time : 00:00:00.0000094

2. 대량 데이터를 사용한 검사---------------

[Instance][Interpret] Matches Count=1048576, Elapsed Time : 00:00:04.9888389

[Instance][Compile] Matches Count=1048576, Elapsed Time : 00:00:05.6970633

[Static][Interpret], Matches Count=1048576, Elapsed Time : 00:00:04.9711290

[Static][Compile] Matches Count=1048576, Elapsed Time : 00:00:04.1505893

[Instance][Assembly] Matches Count=1048576, Elapsed Time : 00:00:03.9443341

계속하려면 아무 키나 누르십시오 . . .

 

결과는 소량의 데이터를 사용한 경우와 대량의 데이터를 사용한 경우로 나누어지며 다음 그래프를 통해 분석 해 보도록 하겠습니다.

 

당연한 결과이나 소량의 데이터를 사용할 경우 상당히 적은 시간이 소요되며 대량의 데이터를 사용할 경우 최소 3초 이상의 시간이 소요되는 것을 확인할 수 있습니다. 먼저 소량의 데이터를 처리하는 경우를 살펴보도록 하겠습니다.

 

 

 

소량의 데이터를 사용할 경우 별도의 어셈블리를 사용하여 처리하는 것이 가장 빨랐으며 인터프리트 방식의 인스턴스 메서드를 사용하는 것이 가장 느리게 나타났습니다.

 

 

대량의 데이터를 사용할 경우도 별도의 어셈블리를 사용하여 처리하는 것이 가장 빨랐으며 컴파일 방식의 인스턴스 메서드를 사용하는 것이 가장 느리게 나타났습니다.

 

적절한 방식의 선택

결론적으로 별도의 어셈블리를 사용하는 방식이 가장 빠르고 인스턴스를 생성하는 방식이 가장 느렸지만 별도의 어셈블리를 사용하는 경우에는 정적 메서드를 사용하는 것처럼 유연하지는 못합니다. 그래서 닷넷에서 정규표현식의 성능을 최대로 향상시키기 위해서 Regex 를 사용할 때는 다음처럼 하기를 권장합니다.

 

1.     동일한 Regex 개체를 반복적으로 생성하기 보다는 정적 메서드를 사용합니다.

2.     정규표현식 패턴을 사용하는 회수가 적고 패턴이 상대적으로 간단한 경우 인터프리트 방식을 사용합니다.

3.     동일한 정규표현식을 수 많은 곳에서 사용할 경우 성능 최적화를 위해 컴파일 된 정규표현식을 사용합니다.

4.     정규표현식 패턴과 옵션을 변경할 필요가 없으며 최상의 성능을 내기 위해서는 외부 어셈블리로 컴파일 하는 방식을 사용합니다.

 

적절한 방식으로 정규표현식을 사용할 경우 훌륭한 성능으로 패턴 매칭 및 유효성 검사, 검색 등에 사용할 수 있을 것입니다. 정규표현식을 통한 문자열 작업은 직접 사용해 보지 않고는 그 강력함을 알 수 없습니다. 우리 모두 정규표현식을 잘 다루는 개발자가 되어 보자구요^^

 

--

저작자 표시
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by kyeongkyun(kobukii) kyeongkyun