- 이번엔 웹디자이너와 배포에 대해서

부족한 번역이지만, 이번엔 웹 디자이너와 배포에 대해서 적어볼까 합니다.


- Visual Studio 2010 Web Designer Improvement

 

Visual Studio 2010의 웹페이지 디자이너는 더 뛰어난 CSS 호환성, HTML ASP.NET 마크업의 코드조각에 대한 추가적인 지원과 Jscript를 위해서 다시 디자인된 인텔리센스등으로 더 향상된 기능을 제공합니다.

 

 

- Improved CSS Compatibility

 

Visual Studio 2010 Visual Web Developer 디자이너는 CSS 2.1 표준과 호환되는데요. 이 디자이너는 HTML 소스와의 통합성을 높이면서 기존의 버전의 Visual Studio보다 전체적으로 더 견고한 기능을 제공합니다. 내부적으로는 앞으로 추가될 렌더링, 레이아웃, 사용성 및 유지보수를 위한 설계적인 측면에서의 향상도 있었습니다.

 

 

- HTML and Jscript Snippets

 

HTML에디터는 인텔리센스를 이용해서 태크이름을 자동완성합니다. 그리고 인텔리센스의 코드조각 기능이 전체 태그와 기타 부분을 완성합니다. VisualStudio 2010에서는 인텔리센스의 코드조각 가능이 C# Visual Basic에 적용되었던 것 처럼 Jscript에 대해서도 지원될 것입니다.

 

VisualStudio 2010에는 200개 이상의 코드조각이 ASP.NET HTML의 일반적인 태그와 필요한 속성(runat=”server” 같은)과 특정 태그들에 공통적인 속성들(ID, DataSourceID, ControlToValidate, Text같은)의 자동완성을 도와줍니다.

 

추가적인 코드조각들을 다운받을 수 있으며, 팀의 공동작업이나 개인작업에 공통으로 쓰이는 마크업의 블록을 코드조각으로 뽑아내서 사용할 수도 있습니다.

 

 

- Jscript IntelliSense Enhancements

 

VisualStudio 2010에선 더 풍부한 편집을 위해서 Jscript의 인텔리센스가 다시 디자인 되었습니다. 이젠 인텔리센스가 registerNamespace같은 메서드나 기타 비슷한 자바스크립트 프레임워크의 기술을 이용해서 동적으로 생성되는 객체를 인식할 수 있습니다. 그리고 방대한 스크립트 라이브러리를 분석하고 화면에 인텔리센스를 표시하는데 걸리는 시간을 사용자가 거의 못 느낄 정도로 향상시켰습니다. 호환성 역시 대단히 향상되어서 외부 라이브러리나 다양한 코딩 스타일도 지원할 수 있게 되었습니다. 문서화를 위한 주석은 타이핑하는 즉시 파싱되어서 인텔리센스에서 바로 활용할 수 있습니다.

 

 

- Web Application Deployment with Visual Studio 2010

 

현재 웹 어플리케이션을 배포하는 작업은 우리가 꿈꾸었던 것만큼 쉽지 않았죠. ASP.NET 개발자는 종종 아래와 같은 문제에 직면하곤 합니다.

 

  FTP같은 기술을 사용해야 하는 공동 호스팅 사이트에 배포하는 경우. 게다가 데이터베이스를 세팅하기 위한 SQL스크립트를 직접 실행해야 하거나 가상 디렉토리같은 IIS세팅도 바꿔야 한다.

  엔터프라이즈 환경에서는 웹 어플리케이션을 배포하는 것으로 끝나지 않고, ASP.NET 설정파일이나 IIS세팅도 수정해야 한다. 데이터베이스 관리자는 데이터베이스를 돌리기 위해 필요한 SQL스크립트를 돌려야 한다. 그런 설치작업은 종종 몇 시간을 잡아먹으며 노동에 가까운 일이 되며, 세세하게 문서화 되어야 한다.

 

VisualStudio 2010은 웹 어플리케이션을 중단없이 배포할 수 있게 도와주는 새로운 기술을 통해서 이런문제들을 해결합니다. 이런 기술들중의 하나가 IIS Web Deployment Tool(MsDeploy.exe)이죠.

 

VisualStudio 2010에서 웹배포에 관련된 요소들은 아래와 같이 크게 분류할 수 있습니다.

 

  Web packaging

  Web.config Transformation

  Database deployment

One-Click Publish for Web applications

 

아래의 섹션들에서 하나씩 자세하게 설명을 해보겠습니다.

 

 

Web Packaging

 

VisualStudio 2010에서는 MSDeploy 툴을 사용해서 어플리케이션을 압축해서 압축파일(.zip)을 생성하는데, 그 파일을 웹 패키지라고 부릅니다. 패키지파일은 어플리케이션에 대한 메타데이터와 아래의 내용들을 포함합니다.

 

 어플리케이션 풀 세팅과 에러페이지 세팅, 등등을 포함하는 IIS세팅

  웹페이지와 사용자 정의 컨트롤, 정적인 컨텐츠(이미지와 HTML파일), 등등을 포함하는 실제 웹 컨텐츠

  SQL 서버의 데이터베이스 스카마와 데이터

  보안 인증서, GAC에 설치할 컴포넌트, 레지스트리 세팅, 등등

 

웹 패키지는 아무 서버에나 복사할 수 있고, IIS 매니저를 사용해서 수동으로 설치할 수 있습니다. 또는, 커맨드라인 명령어나 배포API를 사용해서 자동으로 배포할 수도 있습니다.

 

VS2010에서는 웹패키지를 만들기 위해서 사용가능한 MSBuild task target을 제공합니다. 더 많은 정보는 Vishal Joshi의 블로그에 있는 10 + 20 reasons why you should create a Web Package를 참조하시길 바랍니다.

 

 

Web.config Transformation

 

웹 어플리케이션의 배포를 위해서 VisualStudio 2010에서는 XML Document Transform (XDT)가 도입되었는데, 이 요소는 개발설정에서 Web.config를 읽어와서 제품설정(production setting)으로 변환하게 도와줍니다. 변환에 관련된 세팅은 변환파일인 web.debug.config, web.release.config, 그리고 기타파일(이 파일들의 이름은 MSBuild 설정과 매치된다)등에 명시되어 있습니다. 변환파일에는 Web.config파일을 배포하기 위해 필요한 수정사항들만 포함되어 있습니다. 이런 수정사항들을 간단한 문법으로 명시해주면 되는거죠.

 

아래의 예제는 배포의 릴리즈설정에 의해 생성가능한 web.release.config파일의 일부분인데요. 예제의 내용중에서 Replace키워드를 보면, 배포과정에서 Web.config파일의 connectionString 노드의 값이 어떻게 바뀌어야 하는지 명시해주고 있습니다.

 

<connectionStrings xdt:Transform="Replace">

  <add name="BlogDB" connectionString="connection string detail]" />

</connectionStrings>

 

더 자세한 정보는, Vishal Joshi의 블로그의 Web Deployment: Web.Config Transformation를 보시면 됩니다.

 

 

- Database Deployment

 

VisualStudio 2010의 배포 패키지에는 SQL 서버 데이터베이스에 대한 의존성 역시 포함될 수 있습니다. 패키지 정의의 일부분으로 원본 데이터베이스의 연결문자열을 명시해줄 수 있습니다. 웹 패키지를 만들때 VisualStudio2010에 의해서 데이터베이스 스키마와 선택적으로 데이터에 대해서 SQL 스크립트가 만들어지고 패키지에 포함됩니다. 사용자 정의 SQL 스크립트를 만들어서 서버에서 순차적으로 실행되어야 할 순서를 지정해줄 수도 있습니다. 배포할때, 타겟 서버에 대한 연결문자열을 제공하고, 배포 프로세스에서 이 연결문자열을 이용해서 해당 데이터베이스에 SQL 스크립트를 실행해서 데이터베이스 스키마를 만들고 데이터를 추가합니다.

 

추가적으로, One-Click Publish를 이용하면 어플리케이션이 원격의 공용 호스트에 게시된 후에 데이터베이스를 직접적으로 게시할 수 있도록 배포를 설정할 수 있습니다. 자세한 내용은 Vishal Joshi의 블로그의 Database Deployment with VS 2010를 참조하시면 됩니다.

 

 

- One-Click Publish for Web Application

 

VisualStudio 2010에서는 IIS 원격 관리 서비스를 이용해서 원격의 서버에 웹 어플리케이션을 게시할 수 있도록 도와줍니다. 게시를 위해서 호스팅 계정이나 테스트 서버나 스테이징 서버를 위한 게시 프로파일(publish profile)을 생성할 수 있습니다. 각각의 프로파일은 안전하게 계정정보를 저장할 수 있구요. 그러면 Web One Click Publish 툴바를 이용해서 한번의 클릭으로 타겟서버에 배포할 수 있게 됩니다. VisualStudio2010에서는 MSBuild 커맨드 라인을 통해서도 배포할 수 있는데요. 이 기능을 통해서 지속적인 통합 모델을 이용하는 팀 빌드 환경에 게시작업을 포함할 수 있습니다.

 

더 자세한 정보는, Vishal Joshi의 블로그의 Web 1-Click Publish with VS 2010를 보시면 되구요, VisualStudio 2010에서의 웹 어플리케이션 배포에 대한 비디오 영상을 보려면, Vishal Joshi의 블로그의  VS 2010 for Web Developer Previews를 보시면 됩니다.

 

Welcome to Dynamic C#(8) - DLR이 나무를 사랑하는 이유

C# 2009. 9. 21. 09:00 Posted by 알 수 없는 사용자

- 아아... 이거슨 Love Story.

오늘 말씀드릴 내용은 DLR이 왜 Expression Tree(이하 표현나무)를 처음에는 박대하다가 나중에 가서는 "역시 난 니가 아니면 안되나봐 ㅠ_ㅠ"같은 대사를 날리게 되는지 Jim Hugunin의 증언을 통해서 알려드리고자 합니다. 그럼 지금부터 달콤 쌉싸름하고 무슨말인지도 잘 모르겠는 러브스토리를 한번 감상해보시져.


- 누군가가 필요하긴 했어. 하지만 너 아닌 다른 나무라도 괜찮을 거 같았어.

 지난 시간에 말씀드렸듯이 C# 3.0에 표현나무가 추가된 이유는 쿼리가 실행되는 곳이 어디냐에 따라서 최적화를 하기 위해서 컴파일된 IL코드의 형태보다는 자료구조의 형태가 훨씬 수월하기 때문이었습니다. 그리고 DLR은 닷넷 프레임워크에서 실행되는 동적 언어들이 공동으로 사용할 수 있는 기반을 제공해주고, 각 언어들끼리 그리고 정적언어들과도 서로 대화를 주고 받을 수 있도록 해주기 위한 장치인데요. 그렇다면, 언어별로 내부적인 구현의 차이점이 있을 수 있겠고 한 언어에서 작성한 코드를 다른 언어에서도 사용가능하려면, 둘 사이에 변환과정이 있어야 하겠고 그 변환과정을 수월하게 해줄 수 있는 도구가 필요하겠죠. 어떤 가요? DLR과 표현나무의 러브스토리의 윤곽이 좀 잡히시나요?

Jim Hugunin에 따르면 애초에 DLR을 설계할때 코드를 표현할 공통자료구조로 트리형태를 생각하고 있었다고 합니다. 그리고 타입이 없으면서 런타임에 late-bound되는 노드들로 구성된 트리로 구현을 시작했다고 합니다.  그래서 그 당시에 IronPython에 맞게 구현된 트리에는 타입이 있는 노드가 ConstantExpression딱 하나였다고 합니다. 상수 값이면 뭐든지 가지고 있는 노드 말이죠. 그리고 자신들이 생각하고 있는 트리와 표현나무 사이에는 공통점이 없다고 생각했었다고 합니다. 왜냐면 정적언어에서 쓰는 게 동적언어에는 안 맞을거라고 생각했기 때문이라는 군요. 그래서 독립적으로 트리를 구성해 나갔다고 하네요.


- 하지만, 다른 나무들을 만날 수록 니가 그립더라.

하지만, 다른 언어의 구현을 추가하면서, 기존의 DLR트리에 각 언어의 특징을 제대로 표현하기 힘들어졌다고 하는군요. 각 언어의 특징을 지원하기 위한 방법이 필요한데, 그렇다고 해서 직접적으로 그 언어의 특징을 위한 노드를 추가할 수 는 없었다네요. 아마도 DLR트리는 DLR에서 도는 언어들이 공통적으로 공유하게되는 자료구조인데, 특정언어에만 있는 노드를 추가하면, 점점 지저분해 지기 때문이겠죠.

예를 들면, Python의 print문을 보면, 내부적으로 print문은 Python에서 출력에 대한 걸 어떻게 처리할지에 대해 알고 있는 static 메서드를 호출한다고 하는군요. 그래서 DLR트리에 static 메서드 호출노드를 추가했다고 합니다. 그러면 print호출은 static 메서드를 호출하는 걸로 연결할 수 있으니까요.

그리고 이런 작업을 하다보니깐, 그동안 개발팀이 추가해오던 노드들이 정적인 타입이 적용되는 표현나무의 각 노드랑 딱 들어맞는 다는 걸 깨달았다고 합니다. 그래서 처음부터 다시 만드는 대신에 기존에 잘 쓰고 있는 걸 확장하자고 마음을 먹었고, 기존의 표현나무에 동적인 연산이나 변수, 이름 바인딩, 흐름제어등을 위한 노드를 추가했다고 합니다.

그리고 DLR에서 동작하는 언어가 이런 트리형태를 만들어 내게 되면서 각각의 언어에 맞는 최적화를 수행하는게 수월해졌습니다. 지난 포스트에서 설명드렸던 표현나무를 사용하면서 얻는 장점과 매우 동일한 점입니다. 그래서 각 언어의 컴파일러는 코드를 표현나무의 형태로 만들어서 그 표현나무를 DLR에게 전해준다고 하는 군요.


- 그들은 현재 잘 살고 있답니다.

Jim Hugunin의 PDC2008의 "Dynamic Languages In Microsoft .NET"의 슬라이드를 통해서 각 언어별로 나타나는 표현나무의 모양을 보도록 하겠습니다.

C#에서 구현한 팩토리얼을 표현나무로 옮긴다면 아래와 같다고 하는군요.



그리고 이걸 dynamic을 써서 C#에서 작성하면 아래와 같다고 합니다. 붉은 색으로 변화가 있는 부분이 있죠? 이 부분이 동적인 타입을 적용한 부분인데요, 즉 "==", "*", "-"같은 DLR의 표준메세지 객체를 이용해서 모든 언어에서 사용가능하도록 하면서 C#에서 왔다는 걸 표시해서 C# 바인더를 사용할 수 있게 한다고 합니다.



1과 2를 비교해보시면 어떤 노드가 추가됐는지 확일 할 수 있군요. 그리고 동일한 코드로 IronPython에서 만든 표현나무는 아래와 같습니다.



C#대신에 Python바인더를 사용하고 있구요, 메서드 호출부분에서 global field를 참조하는 부분이 있는데요, 이 부분은 닷넷의 프로퍼티를 통한 static field와 거의 완벽하게 들어 맞는다고 하는군요. 그래서 약간의 코드만 추가하면 된다고 합니다. 그리고 아래는 루비의 표현나무입니다.



루비는 함수형언어 개념의 표현식을 기반으로 하는데요 구문이라는게 없고 모든 루비의 코드 한라인 한라인은 값을 리턴하는 구조라고 하는군요. 그래서 return 같은 구문을 안써도 되므로 표현나무와 자연스럽게 어울린다는 군요.

즉, 각 언어에서 나온 트리가 모양이 조금씩 다르긴 한데 전체적으로 비슷한 구조를 유지하고 있습니다. 이런 각 언어에서 나온 표현나무를 분석해서 모든 트리에서 공유하는 폼으로 만들 수 있다는 군요. 아직 어떻게 정확하게 그렇게 할 수 있는지는 명확하게 설명된게 없어서(혹은 제가 못찾아서-_-, 제가 몰라서-_-) 더 설명드리기는 조금 힘들거 같군요.


- 마치면서

확실히 제게는 조금 벅찬 주제일 수도 있지만, 제가 이해한 범위내에서 최대한 명쾌하게 설명드리려고 노력했습니다. 하지만 틀린부분이 있을 수 있겠죠. 그렇다면 여러분의 지식을 공유하시면서 따쓰한 피드백을 주시기 발합니다. 캬캬캬....


- 참고자료

1. http://blogs.msdn.com/hugunin/archive/2007/05/15/dlr-trees-part-1.aspx
2. http://channel9.msdn.com/pdc2008/TL10/

- 이어서 이어서!

지난 시간에 이어서 Dynamic Data에 관련된 기능들을 보시겠습니다.



- Entity Templates

 

Entity Templates를 이용하면 사용자 정의 페이지를 만들지 않고도 데이터의 레이아웃을 사용자가 편집할 수 있습니다. 페이지 템플릿은 FormView 컨트롤(기존 버전의 Dynamic Data의 페이지 템플릿에서 사용하던 DetailView컨트롤 대신에) DynamicEntity 컨트롤을 사용해서 Entity templates를 렌더링합니다. 이렇게 하면 사용자는 Dynamica Data가 렌더링한 마크업에 대해서 더 많은 제어를 할 수 있게 되죠.

 

아래의 목록은 Entity templates를 포함하고 있는 새로운 프로젝트 폴더의 구조입니다.

 

\DynamicData\EntityTemplates

\DynamicData\EntityTemplates\Default.ascx

\DynamicData\EntityTemplates\Default_Edit.ascx

\DynamicData\EntityTemplates\Default_Insert.ascx

 

EntityTemplates폴더는 모델 객체들을 어떻게 화면에 표시할지에 대한 템플릿을 담고 있습니다. 기본적으로 객체들은 ASP.NET 3.5 SP1 Dynamic Data에서 생성된 DetailsView의 마크업과 유사한 모양의 마크업을 생성해주는 Default.ascx를 이용해서 렌더링되는데요. 다음의 예제는 Default.ascx 컨트롤의 마크업 입니다.

 

<asp:EntityTemplate runat="server" ID="TemplateContainer1">

  <ItemTemplate>

    <tr

      <td>

        <asp:Label ID="Label1" runat="server" OnInit="Label_Init" />

      </td>

      <td>

        <asp:DynamicControl runat="server" OnInit="DynamicControl_Init" />

      </td>

    </tr>

  </ItemTemplate>

</asp:EntityTemplate>

 

기본 템플릿은 전체 사이트의 디자인과 느낌을 바꾸기 위해서 수정가능합니다. 기본 템플릿에는 디스플레이, 수정, 그리고 삽입을 위한 템플릿이 포함되어 있습니다. 새로 추가하는 템플릿들은 데이터객체의 이름을 기반으로 해서 추가될 수도 있는데, 그 타입의 객체의 디자인과 느낌을 수정하고 싶을때가 그렇습니다. 예를들면, 아래와 같은 템플릿을 추가할 수 있습니다.

 

\DynamicData\EntityTemplates\Products.aspx

이 템플릿은 아래와 같은 마크업을 포함하고 있겠죠.

 

<tr>

  <td>Name</td>

  <td><asp:DynamicControl runat="server" DataField="ProductName" /></td>

  <td>Category</td>

  <td><asp:DynamicControl runat="server" DataField="Category" /></td>

</tr>

 

새로운 Entity templates DynamicEntity 컨트롤을 사용하는 페이지에서 화면에 나타납니다. 런타임에 이 컨트롤은 Entity template의 내용으로 대체되는데요. 아래의 마크업은 Entity template를 사용하는 Detail.aspx 페이지 템플릿의 FormView컨트롤입니다. 마크업의 DynamicEntity 요소을 주목해서 보시죠.

 

<asp:FormView runat="server" ID="FormView1"

    DataSourceID="DetailsDataSource"

    OnItemDeleted="FormView1_ItemDeleted">

  <ItemTemplate>

    <table class="DDDetailsTable" cellpadding="6">

      <asp:DynamicEntity runat="server" />

      <tr class="td">

        <td colspan="2">

          <asp:DynamicHyperLink ID="EditHyperLink" runat="server"

              Action="Edit" Text="Edit" />

          <asp:LinkButton ID="DeleteLinkButton" runat="server"

              CommandName="Delete"

              CausesValidation="false"

              OnClientClick='return confirm("Are you sure you want to delete this item?");'

              Text="Delete" />

        </td>

      </tr>

    </table>

  </ItemTemplate>

</asp:FormView>

 

 

- New Field Templates for URLs and E-mail Addresses

 

ASP.NET 4 에서는 새로운 내장 필드 템플릿인 EmailAddress.ascx, Url.ascx가 추가되었습니다. 이 템플릿들은 DataType 속성과 함께 EmailAddress Url로 선언된 필드에 사용됩니다. EmailAddress타입의 객체의 경우에는 필드가 ‘mailto:protocol’를 사용해서 만든 하이퍼링크로 표시되구요, 사용자가 이걸 클릭하면 사용자의 이메일 클라이언트 프로그램을 실행하고 기본메세지를 생성해줍니다. Url타입의 객체는 그냥 일반적인 하이퍼링크로 표시되구요.

 

아래의 예제는 필드를 마크하는 방법을 보여줍니다.

 

[DataType(DataType.EmailAddress)]

public object HomeEmail { get; set; }

 

[DataType(DataType.Url)]

public object Website { get; set; }

  

- Creating Links with the DynamicHyperLink Control

 

Dynamic Data는 사용자가 웹사이트가 접근했을 때 보여지는 URL을 컨트롤하기 위해서 .NET 프레임워크 3.5 SP1에 추가되었던 라우팅 기능을 사용합니다. DynamicHyperLink 컨트롤이 Dynamic Data를 사용한 사이트에 접근하는 링크를 쉽게 만들 수 있도록 도와줍니다. 아래의 예제는 DynamicHyperLink컨트롤을 사용하는 예제입니다.

 

<asp:DynamicHyperLink ID="ListHyperLink" runat="server"

    Action="List" TableName="Products">

  Show all products

</asp:DynamicHyperLink>

 

이 마크업은 Global.asax파일에 정의된 규칙대로 라우팅되는 링크를 만들고, 그 링크는 Products테이블의 List페이지를 가리킵니다. DynamicHyperLink컨트롤은 컨트롤이 위치한 Dynamic Data 페이지에 기반해서 자동으로 테이블의 이름을 명시해줍니다.

 

 

- Support for Inheritance in the Data Model

 

현재 Entity Framework LINQ to SQL 둘다 모두 데이터 모델상에서의 상속을 지원합니다. 예를 들면, InsurancePolicy 테이블이 있는 데이터베이스를 생각해볼 수 있는데요. 추가로 InsurancePolicy와 동일한 필드를 가지고 몇가지 추가적인 필드만 가지는 CarPolicy HousePolicy테이블도 가질 수 있습니다. Dynamic Data는 이렇게 데이터 모델에서 상속관계를 갖는 객체에 대해서 scaffolding을 할 수 있도록 개선되었습니다.

 

 

- Support for Many-to-Many Relationships(Entity Framework Only)

 

엔티티 프레임워크는 다대다 관계를 가지는 테이블을 풍부한 기능으로 지원하고 있고, 관계를 엔티티 객체들간의 컬렉션으로 노출하는 형태로 구현이 되어있습니다. ManyToMany.ascx ManyToMany_Edit.ascx 필드 템플릿은 다대다 관계를 갖는 데이터를 표시하고 수정하는 걸 지원하기 위해서 추가되었습니다.

 

 

- New Attributes to Control Display and Support Enumerations

 

DisplayAttribute를 통해서 필드가 어떻게 화면에 표시될지를 추가적으로 컨트롤 할 수 있습니다.기존 Dynamic Data버전에 있던 DisplayName 속성은 필드의 캡션으로 쓰일 이름을 변경할 수 있도록 지원했었는데요. 새로운 DisplayAttribute 클래스는 필드를 표시하는데 있어서 필드의 데이터를 어떻게 정렬할 것인지, 필드에 필터를 적용할 것인지 같은 옵션을 제공합니다. 그리고 이 속성은 추가적으로 GridView컨트롤의 레이블, DetailsView컨트롤의 이름, 필드의 도움말, 필드의 워터마크(필드가 텍스트입력을 받는다면)등에 대한 독립적인 제어를 제공합니다.

 

EnumDataTypeAttribute 클래스는 필드를 열거형데이터 타입에 연결해주기 위해서 추가되었습니다. 이 속성을 필드에 주게되면, 열거형 타입을 명시하게 되는데요. Dynamic Data에서는 열거형 값을 표시하고 수정하는데 Enumeration.ascx를 사용합니다. 이 템플릿은 데이터의 값을 열거형의 이름과 연결합니다.

 

 

- Enhanced Support for Filters

 

Dynamic Data 1.0boolean열과 외래키열에 대한 내장필터를 갖고 릴리즈 되었었습니다. 하지만,이 필터들은 열을 화면에 표시할지, 열의 데이터를 어떤 순서로 출력할지 명시해줄 수는 없었는데요. 새로운 DisplayAttribute 속성은 이 문제를 화면에 열을 필터로 표시할지 그리고 어떤 순서로 표시할지 설정할 수 있게 함으로써 해결해줍니다.

추가적으로 향상된 부분은 필터링을 지원하는 부분이 웹폼의 QueryExtender를 이용하도록 재작성됐다는 점입니다. 이로써 필터가 사용될 데이터 소스 컨트롤을 몰라도 필터를 작성할 수 있습니다. 이런 확장과 더불어서 이젠 필터도 템플릿 컨트롤로 작성되게 되어서 새로운 필터를 작성해서 추가할 수 있게 되었습니다. 마지막으로, DisplayAttribute 클래스는 UIHint가 기본적인 필드템플릿을 오버라이드할 수 있게 지원하는 것 처럼, 기본적인 필터를 오버라이드할 수 있도록 지원합니다.



- 마치면서

다음번에는 웹 디자이너와 배포에 어떤 새로운 기능들이 추가되고 개선되었는지에 대해서 말씀드리겠습니다!

Welcome to Dynamic C#(7) - 아낌없이 표현해 주는 나무

C# 2009. 9. 12. 08:30 Posted by 알 수 없는 사용자

- 럭키 세븐 -_-v

기분도 좋게 일곱번째 글이 되는 오늘은 아낌없이 표현해주는 나무, expresion tree를 가지고 이야기 해보도록 하겠습니다. LINQ의 뒤를 든든하게 받치고 있는 요소지만, 전면에 거의 드러나지 않아서 이게 뭔지 알아보려고 노력안하면 볼일이 없는 친구입니다. 저 역시 LINQ로 프로젝트를 진행하면서도 얉은 지식과 호기심으로 인해서 이런게 있다는 것도 모르고 있었는데요. 그리고 6월에 있었던 세미나에서는 '컴파일러같은 툴 개발자들에게나 적합한 기능인거 같다'는 망언을 하기도 했었습니다. 뭐 100% 틀린 말은 아니겠지만, 이해가 부족한 탓에 나온 망언이었던거니 혹시 마음상한 분 있으셨다면 여친한테 밴드라도 붙여달라고 하시면 좋을거 같네요. 여친없으시면 어머니한테라도...


- 표현해주는 나무나 빨랑 설명해봐

일단 expression tree는 실행가능한 코드를 데이터로 변환가능한 방법을 제공해주는 요소입니다. 이렇게 데이터로 변환하게 되면 컴파일하기 전에 코드를 변경한다거나 하는 일이 매우 수월해지는데요. 예를 들면, C#의 LINQ 쿼리식을 sql 데이터베이스같이 다른 프로세스상에서 수행하는 코드로 변환하는 경우 말이죠.

Func<int, int, int> function = (a, b) => a + b;

위와 같은 문장은 머리,가슴,배는 아니지만 세부분으로 구성됩니다.

1. 선언부 : Func<int, int, int> function
2. 등호 연산자 : =
3. 람다 식 : (a, b) => a + b;

현재 변수 function은 두 숫자를 받아서 어떻게 더하는지를 나타내는 코드를 참조하고 있습니다. 그리고 위의 람다 표현식을 메서드로 표현해본다면 대략 아래와 같은 모양이 되겠죠.

public int function(int a, int b)
{
    return a + b;
}

Func는 System네임스페이스에 아래와 같이 선언되어 있습니다.

public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2); 

이 선언이 우리가 두개의 숫자를 더하는 간단한 람다식을 선언하는 걸 도와준 셈이죠.


- 근데, 어케 코드에서 표현해주는 나무로...?

위에서 우리는 실행가능한 코드를 function이라는 변수를 통해서 참조할 수 있다는걸 봤습니다. 근데, expression tree는 실행가능한 코드나 아니고, 자료구조의 한 형태입니다. 그러면, 어떻게 표현식을(코드를) expression tree로 변환하는 걸까요? 그래서 LINQ가 좋은걸 준비해뒀습니다.

using System.Linq.Expressions;

....

Expression<Func<int, int, int>> expression = (a, b) => a + b; 

위와 같이만 하면, 람다식이 expression tree로 쑉~! 하고 변환이 됩니다. 그럼 이걸 좀 더 눈에 보이게 살펴볼까요? 여기에 가시면, C#으로 구현된 예제들을 받을 수 있는데요, 예제중에 ExpressionTreeVisualizer(이하, ETV)라는게 있습니다. expression tree를 TreeView컨트롤을 이용해서 보기쉽게 쪼개주는 놈이죠. (a, b) => a + b;를 한번 확인해볼까요?



위의 간단한 예제코드는, Expression<TDelegate>클래스를 사용하고 있는데요, 그 클래스의 4가지 프로퍼티를 ETV를 통해서 확인해보실 수 있습니다. 좀 더 확실하게 하기 위해서 '-'를 눌러서 다 접어볼까요?



위 그림을 보시면, 딱 4가지만 남아있죠?

  • Body : expression의 몸체를 리턴한다.
  • Parameters : 람다식의 파라미터를 리턴한다.
  • NodeType : expression tree의 특정노드의 ExpressionType을 리턴한다. ExpressionType은 45가지의 값을 가진 열거형타입인데, expression tree에 속할 수 있는 모든 노드의 목록이 포함되어 있다. 예를 들면, 상수를 리턴하거나, 파라미터를 리턴한다거나, 둘 중에 뭐가 더 큰지 결정한다거나(<,>), 두 값을 더한다거나(+) 하는 것들이 있다.
  • Type : expression의 정적인 타입을 리턴한다. 위의 예제같은 경우에는 Func<int, int, int>가 되겠다.

아래와 같은 코드를 통해서 위의 프로퍼티들의 값을 확인해볼 수 있습니다.

using System;
using System.Linq.Expressions;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            Expression<Func<int, int, int>> expr = (a, b) => a + b;

            BinaryExpression body = (BinaryExpression)expr.Body;
            ParameterExpression left = (ParameterExpression)body.Left;
            ParameterExpression right = (ParameterExpression)body.Right;

            Console.WriteLine(expr.Body);
            Console.WriteLine("표현식의 왼쪽 : {0}\n노드의 타입 : {1}"
                + "\n표현식의 오른쪽 : {2}\n몸체의 타입 : {3}",
                left.Name, body.NodeType, right.Name, body.Type);
        }
    }
}


그리고 실행결과는 아래와 같습니다.



결과에서 expression의 모든 요소들이 각각의 노드로 이루어진 자료구조라는 것을 확인해보실 수 있습니다. 그리고 반대로 expression tree를 코드로 변환해서 실행하는 것도 매우 간단합니다. 아래와 같이 딴 한줄이면 됩니다.

int result = expr.Compile() (3,5); 


- 근데 당췌 왜 코드를 나무로 바꾸는건데? 식목일이냥?

이제 expression tree에 대해서는 조금 익숙해지셨을 겁니다. 특히 이 expression tree가 LINQ to SQL에 아주 중요한 역할을 하는데요, 일단 아래의 LINQ to SQL 쿼리문을 보시져~.

var query = from u in db.Users
     where u.nickname == "boram"
     select new { u.uId, u.nickname };


위 쿼리문의 결과로 반환되는 타입을 확인해보면 아래와 같습니다.



넵 바로 IQueryable인데요, IQueryable의 정의를 확인해보면 아래와 같습니다.

public interface IQueryable : IEnumerable
{
    Type ElementType { get; }
    Expression Expression { get; }
    IQueryProvider Provider { get; }

즉, 멤버로 Expression타입의 프로퍼티를 가지고 있습니다. IQueryable의 인스턴스는 expression tree를 가지고 있도록 설계된 거죠. 그 expressio tree가 바로 코드로 작성한 LINQ쿼리문의 자료구조입니다. 그런데, 왜 이렇게 LINQ to SQL쿼리를 expression tree형태로 가지고 있는걸까요? 그 핵심은, A라는 프로그램의 코드에 LINQ to SQL쿼리가 있다고 했을때, 실제로 이 쿼리가 수행되는 곳이 A가 아니라 데이터베이스 서버라는 점에 있습니다. 즉, 프로그램에서 직접실행되는게 아니라 데이터베이스가 알아들을 수 있는 SQL쿼리형태로 변환을 해서, 그 쿼리를 데이터베이스에게 날려서 쿼리된 데이터를 받아온다는 거죠. 위의 LINQ to SQL쿼리는 대략 아래와 같은 SQL문으로 변환이 됩니다.

SELECT [t0].[uId], [t0].[nickname]
FROM [dbo].[Users] AS [t0]
WHERE [t0].[nickname] = @p0 

프로그램내에 존재하는 쿼리표현식은 SQL 쿼리로 변환되어서 다른 프로세스에서 사용되게끔 문자열 형태로 보낸다는 거죠. 그러면, 위에서 설명드린대로 실제 쿼리는 데이터베이스의 프로세스내에서 처리가 되구요. 즉, IL코드를 SQL쿼리로 변환하는 거 보다, expression tree같은 자료구조형태가 변환하기 훨씬 쉬울뿐더러, 최적화 같은 중간처리도 훨씬 용이하다는 겁니다. 하지만 LINQ to Objects를 통해서 쿼리를 해보면 결과의 타입은 IEnumerable<T>입니다. 왜 얘네들은 IQueryable<T>가 아닐까요? IEnumerable<T>의 정의를 보면 아래와 같습니다.

public interface IEnumerable<T> : IEnumerable
{
    IEnumerator<T> GetEnumerator();

즉, Expression타입의 프로퍼티가 없습니다. 왜냐면, LINQ eo Objects는 같은 프로세스내에서 처리될 객체들을 대상으로 쿼리를 하기 때문에 다른 형태로 변환될 필요가 없기 때문입니다. 그렇다면, 굳이 expression tree같은 자료구조로 변환할 필요가 없겠죠. 그래서 대략 아래와 같은 규칙이 성립합니다.
  • 코드가 같은 프로그램(또는 프로세스)내에서 실행되는 경우라면 IEnumerable<T>
  • 쿼리 표현식을 다른 프로그램(또는 프로세스)에서 처리하기 위해서 문자열 형태로 변환해야 한다면 expression tree를 포함하는 IQueryable<T>를 사용 


- 마치면서

일단 오늘은 C# 3.0에 포함되었던 expression tree에 대해서 설명을 드렸습니다. DLR이랑 dynamic이야기 하다가 난데없이 삼천포로 빠진 느낌이 드시겠지만(저는 경남 진주에 살았었는데, 아버지 따라 삼천포 자주 갔었습니다....이 이야기는 왜하는 거지..-_-), DLR에서 expression tree가 중요하게 사용되고, 또한 C# 3.0을 사용해보신 분들이라도 expression tree에 대해서 제대로 못짚고 넘어간 분들도 많으리라 생각합니다. 저 역시 그랬구요. 아무튼, 도움되셨기를 바라면서 다음에 뵙죠!


- 참고자료

1. http://blogs.msdn.com/charlie/archive/2008/01/31/expression-tree-basics.aspx

- 이건 또 뭥미

이 포스트 시리즈는 http://www.asp.net/learn/whitepapers/aspnet40/ 에 올라와있는 "ASP.NET 4.0 and Visual Studio 2010 Web Development Beta 2 Overview"를 번역하는 시리즈입니다. 번역이라서 조금은 딱딱할 수도 있고, 내공의 부족으로 제대로 번역이 힘든 부분도 있습니다.(반드시 있습니다-_-) 따쓰한 피드백으로 격려를 부탁드립니다. 냐하하하하. 두부분으로 나눠서 번역을 맡았는데요, 제가 맡은 부분은 Dynamic Data, Studio 2010 Web Designer Improvement, Web Application Deployment with Visual Studio 2010 이렇게 세부분입니다. 그럼 시작해볼까효?


- Dynamic Data

 

Dynamic Data 2008년 중반에 릴리즈되었던 .NET 프레임워크 3.5 서비스팩 1에서 처음 소개가 됐습니다. 이 기술은 데이터 중심 개발을 하는데 있어서 많은 유용한 기능을 제공하는데요. 예를 들면 아래와 같습니다.

 

l  데이터 중심의 웹사이트를 RAD(빠른 속도의 어플리케이션 개발)처럼 작성

l  데이터 모델에 명세된 제약조건을 기반으로 자동으로 유효성검사

l  Dynamic Data 프로젝트의 일부인 필드 템플릿을 이용해서 GridView DetailsView의 자동생성된 필드의 마크업을 쉽게 변경할 수 있음

 

더 상세한 정보는 MSDN에 있는 Dynamic Data documentation을 참조하시면 됩니다.

 

ASP.NET 4 에선 기존의 기능보다 더 강력해진 지원으로 데이터중심의 웹사이트를 더 빠르게 개발할 수 있도록 도와줍니다.

 

- Enabling Dynamic Data for Existing Projects

 

.NET 프레임워크 3.5 서비스팩 1 에 추가되었던 Dynamic Data는 아래와 같은 새로운 기능들을 소개했었죠.

 

l  필드 템플릿 데이터 바운드 가능한 컨트롤에 대해서 데이터 타입에 기반한 템플릿을 제공한다. 필드 템플릿은 기존에 각각에 필드에 템플릿 필드를 이용해서 설정해줘야 하던것에 비해서 컨트롤의 모양을 입맛에 맞게 커스터마이즈하는 좀 더 간편한 방법을 제공한다.

l  유효성검사 데이터 클래스에 어트리뷰트를 이용해서 값의 유무, 범위 체크, 타입 체크, 정규식을 이용한 패턴매칭, 사용자 정이 유효성검사와 같은 일반적인 경우에 대한 유효성검사를 명시해줄 수 있다.

 

하지만, 이런 기능들은 아래와 같은 요구사항이 있었습니다.

 

l  데이터 엑세스 레이어는 반드시 Entity Framework LINQ to SQL이어야 한다.

l  데이터 소스 컨트롤은 EntityDataSource LinqDataSouce컨트롤 이 두가지 외에는 지원되지 않는다.

l  위와 같은 기능들을 모두 사용하기 위해서는 필요한 파일들이 모두 포함된 템플릿인 Dynamic Data Dynamic Data Entities를 통해 생성된 웹 프로젝트여야만 했다.

 

ASP.NET 4에 추가될 Dynamic Data에서 가장 초점을 둔 부분중의 하나는 어떤 ASP.NET 어플리케이션이라고 해도 Dynamic Data의 기능을 활용할 수 있도록 한다는 것 입니다. 아래의 예제코드는 이런 목표에 맞게, 기존에 존재하던 페이지에 Dynamic Data의 기능을 활용하기 위해서 명시해줘야 하는 코드입니다.

 

<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="True"

    DataKeyNames="ProductID" DataSourceID="LinqDataSource1">

</asp:GridView>

<asp:LinqDataSource ID="LinqDataSource1" runat="server"

    ContextTypeName="DataClassesDataContext" EnableDelete="True" EnableInsert="True"

    EnableUpdate="True" TableName="Products">

</asp:LinqDataSource>

 

페이지의 코드비하인드에서 이 컨트롤들의 Dynamic Data기능을 켜려면 아래와 같은 코드를 반드시 추가해야 합니다.

 

GridView1.EnableDynamicData(typeof(Product));

 

GridView 컨트롤이 수정모드에 있을 때, 사용자가 입력한 값이 적절한 포맷인지 아닌지를 Dynamic Data가 자동으로 검사를 하고, 포맷에 맞지 않는다면 에러메세지를 보여줍니다.

 

이 기능은 입력모드에서 사용될 기본값을 명시해줄 수 있는 것 같이 다른 장점도 가지는데요. Dynamic Data를 쓰지 않고 필드의 기본값을 설정하려면, 이벤트를 걸어야 되고, FindControl등의 메서드를 이용해서 컨트롤을 찾아야 되고, 값을 설정해야 하는등의 작업을 해야합니다. 하지만 ASP.NET 4에서는, EnableDynamicData메서드에서 아래의 예제코드에서 처럼 어떤 필드에도 기본값을 설정해줄 수 있습니다.

 

DetailsView1.EnableDynamicData(typeof(Product), new { ProductName = "DefaultName" });

 

 

- Declarative DynamicDataManager Control Syntax

 

다른 ASP.NET컨트롤들이 그렇듯이 DynamicDataManager컨트롤도 이제는 코드에서 뿐만 아니라 선언적으로 속성을 명시해줄 수 있게 개선되었습니다. 아래 예제에서 DynamicDataManager 컨트롤의 마크업을 확인할 수 있습니다.

 

<asp:DynamicDataManager ID="DynamicDataManager1" runat="server"

    AutoLoadForeignKeys="true">

  <DataControls>

    <asp:DataControlReference ControlID="GridView1" />

  </DataControls>

</asp:DynamicDataManager>

 

<asp:GridView id="GridView1" runat="server"

</asp:GridView>

 

이 마크업은 DataControls섹션에 나와있듯이 GridView1 Dynamic Data기능을 적용시킵니다.



- 마치면서

쫌 이상해 보이긴 하네요-_-ㅋ 다음 포스트에서는 Entity Templates부터 시작해서 템플릿에 대한 내용을 설명드리도록 하겠습니다.

[JumpToDX11-4] ID3D11View

DirectX 11 2009. 9. 7. 18:00 Posted by 알 수 없는 사용자

 

이번 회에서는 일단 소스를 좀 나열해 보겠습니다.
일단 변수들은 다음과 같습니다.




이어지는 상황은 다음과 같습니다


 

BackBuffer 를 설정했습니다. 일단 'View' 라는 키워드에 주목해 주시기 바랍니다.
그리고 다음에 이어지는 상황을 다시 보겠습니다. 

 

 




이어졌던 소스는 Depth-Stencil Buffer 생성을 위한 작업이였습니다.

역시나 낯선 개념이 등장하는데, 바로 'View' 입니다.
우리가 앞서 생성했던 BackBuffer 와 Depth-Stencil Buffer 에는 CreatexxxxxxxView() 형태로
작업을 해주고 있습니다.

하나의 뷰는 렌더링 작업 중에 파이프라인에서 접근할 수 있는 리소스 일부분을 의미합니다.

더 정확한 개념을 위해서 아래의 계층구조를 한번 살펴보시기 바랍니다.






우리가 사용하는 모든 리소스들은 사실 ID3D11Resource 를 상속받아서 구성됩니다.

텍스쳐나 각종 버퍼 등이 이에 속한다고 할 수 있습니다.

( ID3D11Buffer, ID3D11Texture )

 

ID3D11View 를 상속받아서 구현되는 것들은 ID3D11RenderTargetView, ID3D11DepthStencilView, ID3D11ShaderResourceView 가 있습니다.

 

나열하고 보니깐 약간 감이 오시지 않으십니까?

이렇게 분리가 되었는데, 사실 ID3D11View ID3D11View::GetResource() 라는 멤버함수를 가지고 있습니다.

 

결국 ID3D11View ID3D11Resource 와 개념적으로 메모리 데이터는 동일하다고 볼 수 있습니다.

그러다 보니 이 두가지 인터페이스를 구별해서 설명하기가 무척 난해합니다.
비슷하면서도 실질적으로 수행되는 역활에는 엄연한 구분이 있으니 말이죠...

 

이렇게 분리함으로써 역할 구분이 명확해서 코드의 품질이 향상되었다라고 말할 수 있습니다.
조금 더 좋은 방향으로 생각해 본다면,
아마도 내부적으로 조금 더 최적화 된 위치에 메모리를 할당해 주지 않을까? 라는 의심도 해봅니다.
( 왜 그런지 이유를 꽤 오래 생각했지만, 답을 찾지 못했습니다..T.T )

 

 

참고로 얘기드리면,

DirectX9 버전까지 유용하게 사용되던 메모리 풀의 개념이 이제는 CPU 에서 읽기/쓰기 작업,
GPU
에서 읽기/쓰기 작업이 가능여부를 설정하는 개념형태로 변경
되었습니다.

이것에 대한 얘기는 다음 번에 저나 다른 분이 해주실 겁니다.

( DirectX11 파트에는 저 말고도 한분이 더 계십니다…^^ )

 

위의 코드에서 View 계열들은 GPU 만 읽기/쓰기 작업이 가능한 형태로 그래픽 카드상에
메모리를 할당했습니다.


결국 위의 작업은 바로 아래에 나오는 작업을 위해서 필요했던 것입니다.



보시듯이 모두 View 계열의 인터페이스를 설정했습니다.



< 마치면서... >
다음주는 한주 쉬게 될 것입니다.
스터디 내부 발표가 있어서...쿨럭~



 

Welcome to Dynamic C#(6) - Return to Dynamic (2)

C# 2009. 9. 3. 13:36 Posted by 알 수 없는 사용자

- 복습.

지난시간에서 이야기 했던 부분을 조금 이어서 이야기하자면요, dynamic이라는 타입이 생겼고 이 타입은 런타임에 가서야 실제로 담고있는 타입이 뭔지 수행하고자 했던 연산이 존재하는지 등을 알 수 있습니다. 그리고 닷넷 프레임워크 내부적으로는 dynamic이라는 타입은 없으며, object타입에 dynamic 어트리뷰트가 붙어서 런타임에게 적절한 동적 연산을 수행하도록 알려주도록 하고 있었습니다. 그리고 dynamic과 관계된 연산을 만나면, 컴파일러는 DLR의 call site를 이용하는 코드를 생성하구요 DLR에 정의된 기본연산들중에 C#에 알맞도록 상속된 클래스를 생성하고 그 연산을 call site를 통해서 호출하도록 코드를 생성해줍니다.

그러면 잠깐 실제로 동적연산을 호출하면 런타임에 어떤 절차를 거치게 되는지 알아보도록 하겠습니다.


- 복습은 거기까지.

dynamic d = ......;
d.Foo(1, 2, d); 

위와 같은 코드가 실행된다고 할때 대략 아래와 같은 절차를 따르게 됩니다.

1. DLR이 사용자가 넘겨주는 매개변수의 타입(int, int, dynamic)으로 요청받은 액션(InvokeMember)이 캐시되어 있는지 확인합니다. 캐시에 저장이 되어 있다면, 캐시되어있던 걸 리턴합니다.

2. 캐시에 해당되는 내용이 없다면, DLR은 액션을 요청받는 객체가 IDynamicObject(이하 IDO)인지 확인합니다. 이 객체는 스스로 어떻게 동적으로 바인딩하는지를 알고 있는 객체입니다.(COM IDispatch object, 루비나 파이썬의 객체나 IDynamicObject 인터페이스를 구현한 닷넷 객체들 처럼). 만약 IDO라면 DLR은 IDO에게 해당 액션을 바인딩해달라고 요청합니다. IDO에게 바인딩을 요청한 뒤에 받는 결과는 바인딩의 결과를 나타내주는 expression tree입니다.

3. IDO가 아니라면, DLR은 language binder(C#의 경우는 C# runtime binder)에게 해당 액션을 바인딩해줄 것을 요청합니다. 그러면 C# runtime binder가 그 액션을 바인딩하고 바인딩의 결과를 expression tree로 리턴해줍니다.

4. 2번이나 3번이 수행된 다음엔 결과로 받은 expression tree가 DLR의 캐시속으로 통합되고 같은 형태의 요청이 다시 들어온다면 바인딩을 위한 절차를 수행하지 않고 캐시에 저장된 결과를 가지고 실행하게 됩니다.


그리고 이어서 C# runtime binder에 대해서 소개해 드릴텐데요, C# runtime binder는 무엇을 어디에 바인딩할지를 결정하는 심볼테이블을 reflection을 이용해서 생성합니다. 만약에 컴파일타임에 타입이 정해진 매개변수라면 C# 액션의 정보에 해당 타입으로 기록되고 런타임시에 바인딩될때 그 매개변수는 기록된 타입으로 사용될 수 있겠죠. 근데 만약에 컴파일 타임에 dynamic으로 결정된 매개변수라면(dynamic타입의 변수나, dynamic을 리턴하는 표현식), runtime binder는 reflection을 이용해서 그 매개변수의 타입을 알아내고, 알아낸 타입을 그 매개변수의 타입으로 사용하게 됩니다.

- 심볼테이블?

심볼테이블은 컴파일러나 인터프리터가 코드를 번역하기 위해서 사용하는 자료구조인데요. 코드의 변수명, 메서드등의 식별자를 각각 타입이나 범위, 그리고 메모리에서의 위치등을 기록합니다. 아래의 표는 위키피디아에 있는 심볼테이블의 예제인데요, 메모리의 주소와 심볼의 타입과 심볼의 이름으로 구성되어 있습니다. 

Address Type Name
00000020 a T_BIT
00000040 a F_BIT
00000080 a I_BIT
20000004 t irqvec
20000008 t fiqvec
2000000c t InitReset
20000018 T _main
20000024 t End
20000030 T AT91F_US3_CfgPIO_useB
2000005c t AT91F_PIO_CfgPeriph
200000b0 T main
20000120 T AT91F_DBGU_Printk
20000190 t AT91F_US_TxReady



runtime binder는 심볼테이블을 필요할때 필요한 만큼 만드는데요, 위의 짧은 예제에서 처럼 Foo라는 메서드를 호출하는 경우라면 runtime binder는 d의 런타임 타입에 대해서 Foo라는 이름을 가진 멤버들을 모두 로드합니다. 그리고 형변환역시 요구되는 형변환들을 모두 심볼테이블로 로드합니다. 그리고 런타임 바인더는 C# 컴파일러가 하는 것과 동일한 오버로딩 판별알고리즘을 수행합니다. 그리고 컴파일시에 받는 것과 동일한 문법을 사용하며, 동일한 에러, 동일한 예외를 출력합니다. 그리고 마지막으로 이런과정을 거친 결과를 expression tree로 생성하고 DLR에게 리턴해줍니다. 단, 여기서 사용하는 expression tree는 C# 3.0의 expression tree를 확장한 것입니다. 기존의 expression tree에 동적인 연산, 변수, 이름 바인딩, 흐름제어를 위한 노드들이 추가된 거죠.


- 마치면서

뭔가, 짧고 설명도 어색한 포스트인거 같네요;;; 이번시간을 통해서 DLR이 코드를 실행하기 위해서는 코드를 내부적으로 expression tree라는 형태로 가지고 있음을 알아봤습니다. 다음시간엔 C# 3.0에서 처음등장한 expression tree와 DLR에서 사용하는 expression tree에 대해서 설명을 드리도록 하겠습니다.


- 참고자료

1. http://blogs.msdn.com/samng/archive/2008/10/29/dynamic-in-c.aspx
2. http://en.wikipedia.org/wiki/Symbol_table

Welcome to Dynamic C#(5) - Return to Dynamic.

C# 2009. 8. 26. 15:02 Posted by 알 수 없는 사용자

- 그때 너 너무 대충하더라

제가 맨 처음 dynamic에 대한 포스트로 Welcome to Dynamic시리즈를 시작했는데요. 뭐랄까 너무 추상적인 내용위주로 진행했다는 생각이 들더군요. 그리고 공부를 더 하다보니 그런 생각이 더 확실해지더군요. 없는 input에서 output이 나올수는 없는거니 당연한 이야기 겠지요. 캬캬캬. 앞으로는 dynamic과 DLR에 대해서 이야기를 조금 진행해보려고 합니다. 그래봤자 여전히 별 내용없거나 다른분들이 주는 insight를 그대로 전해주는 역할 이상은 못할지도 모르지만 일단 늘 그래왔듯이 노력해보겠습니다. 따쓰한 피드백을. ㅋㅋㅋㅋ


- dynamic?

dynamic d = ....;
d.Foo();

여기서 지역변수인 d는 dynamic타입을 가집니다. dynamic은 엄연히 컴파일러가 지원하는 타입이고, 타입이름이 들어갈 수 있는 곳에는 어디든지 dynamic이라고 명시해줄 수 있습니다. 즉, 실제타입이 동적으로 결정되는 것을 의미하는 정적인 타입인거죠. 차이점이라고 한다면, Foo라는 메서드를 호출하는 IL코드를 바로 만든다기 보다는 DLR과 C# 런타임 바인더를 통해서 dynamic call site라는 걸 호출되는 지점에서 생성합니다. 

이런 기능을 통해서 여러분이 기존에 써오던 친숙한 방법과 모습으로 파이썬이나 루비, 혹은 "스스로 어떻게 실행해야 하는지 알고있는" 객체들을 사용할 수 있게 해줍니다. 개인적으로는 이 부분이 꽤나 중요한 부분이라고 생각합니다. 다른 동적언어들이 있는데, 굳이 C#에 이런 기능이 들어가는건 앤더스 헬스버그의 철학답게, 기존에 잘 사용해오던 언어에 새로운 기능을 잘 통합시켜서 한 부분에만 특화된 언어보다 기존의 언어에서도 새로운 기능을 사용할 수 있게 해주는 거겠죠. 그래서 기존에 잘 사용해오던 언어가 새로운 요구에 발맞추는 새로운 표현법을 계속해서 잘 통합시켜 나가면서 생명력을 유지할 수 있게 말이죠.

위에서 말씀드렸듯이 dynamic은 분명히 존재하는 타입이고 컴파일러도 잘 알아듣는 타입이지만, 현재까지의 모습으로 봤을땐 실제로는 존재하지 않는 타입입니다. DLR과의 연동을 통해서 가능한 동적인 프로그래밍을 문법적으로 편리하게 만들어주는 syntatic sugar같은 역할이라고 볼 수 있을까요? 일단 아래코드를 보시져.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace ConsoleApplication2
{
    class Program
    {
        public dynamic DynamicCall(dynamic d)
        {
            object obj = 5;
            return d.Foo();
        }

        static void Main(string[] args)
        {           
        }
    }
}


그리고 이 코드에서 타입위에 마우스를 가져가 보시져. object와 dynamic을 비교해보겠습니다.




object위에 마우스를 올렸을때는 "class System.Object"라고 나오는데, dynamic에는 그런 표시가 없죠? 그럼 우리의 심증을 물증으로 굳혀보겠습니다. 위의 코드를 컴파일한 코드를 리플렉터에서 보면 아래와 같습니다.

----- 리스트 1 -----

[return: Dynamic]
public object DynamicCall([Dynamic] object d)
{
    if (<DynamicCall>o__SiteContainer0.<>p__Site1 == null)
    {
        <DynamicCall>o__SiteContainer0.<>p__Site1 =
            CallSite<Func<CallSite, object, object>>.Create(
                new CSharpInvokeMemberBinder(
                    CSharpCallFlags.None, "Foo", typeof(Program), null,
                    new CSharpArgumentInfo[] {
                        new CSharpArgumentInfo(CSharpArgumentInfoFlags.None, null) }));
    }

    return <DynamicCall>o__SiteContainer0.
                    <>p__Site1.Target(<DynamicCall>o__SiteContainer0.<>p__Site1, d);
}

dynamic이라는 타입은 싹 사라지고 object만 덩그러니 있는걸 확인할 수 있습니다. 그리고 dynamic이라고 알려주는 지시자같은게 붙어있는걸 보실 수 있습니다. 즉 beta1기준으로 현재에는 dynamic은 엄연히 하나의 타입이지만, 닷넷 프레임워크 내부적으로는 dynamic이라는 타입이 존재하지 않는다는 말입니다. 즉, 저렇게 dynamic이라고 표시가 된 객체는 컴파일러가 런타임에게 동적으로 처리되어야 한다는 걸 알려주는게 되겠죠.

그리고 위에서 말씀드렸듯이 컴파일러는 dynamic과 관계된 연산을 만나게 되면, DLR을 통해서 DLR의 call site를 이용하는 코드를 생성합니다. 코드에 보시면 'SiteContainer', 'CallSite'같은게 보이시죠? DLR에 기반한 동적언어에서 어떤 동적 연산을 호출하면 그 코드는 DLR이 이해할 수 있는 기본적인 연산으로 번역되고, 그 기본적인 연산을 적용할 대상에 따라서 Python객체에 하려면 Python Binder로, 기본적인 .NET 객체에는 Object Binder로, C#은 C#런타임 바인더로 적용하게 됩니다. 그 기본적인 연산의 목록은 현재 아래와 같습니다.

----- 리스트 2 -----

namespace System.Dynamic
{
    public class DynamicObject : IDynamicMetaObjectProvider
    {
        protected DynamicObject();

        public virtual IEnumerable<string> GetDynamicMemberNames();

        public virtual DynamicMetaObject GetMetaObject(Expression parameter);

        public virtual bool TryBinaryOperation(BinaryOperationBinder binder, object arg, out object result);

        public virtual bool TryConvert(ConvertBinder binder, out object result);

        public virtual bool TryCreateInstance(CreateInstanceBinder binder, object[] args, out object result);

        public virtual bool TryDeleteIndex(DeleteIndexBinder binder, object[] indexes);

        public virtual bool TryDeleteMember(DeleteMemberBinder binder);

        public virtual bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result);

        public virtual bool TryGetMember(GetMemberBinder binder, out object result);

        public virtual bool TryInvoke(InvokeBinder binder, object[] args, out object result);

        public virtual bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result);

        public virtual bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value);

        public virtual bool TrySetMember(SetMemberBinder binder, object value);

        public virtual bool TryUnaryOperation(UnaryOperationBinder binder, out object result);
    }
}


위의 목록에서 보시면 TryInvokeMember메서드가 있고 인자로는 InvokeMemberBinder를 받는게 보이시져? 그리고 위의 리플렉터에서 뽑은 리스트1을 보시면, Create메서드의 인자로 CSharpInvokeMemberBinder를 생성하고 있습니다. 그리고 CSharpInvokeMemberBinder를 따라가보면, base클래스가 InvokeMemberBinder가 나옵니다. 즉, DLR이 이해할 수 있는 기본연산이 C# 바인더를 통해서 실행되고 있을음 유추해볼 수 있습니다.

그리고 리스트1에서 "<>p_Site1" 이라는 걸 따라가보면 선언이 아래와 같습니다.

public static CallSite<Func<CallSite, object, object>> <>p__Site1;

즉, static 필드인데요. 델리게이트도 담고 있습니다. 이게 Foo메서드 호출에 대한 dynamic call site를 가지고 있는 필드입니다. 위의 리스트1의 코드를 보시면, 이 <>p_Site1에 저장된 내용을 Target메서드를 통해서 호출하고 있는 모습을 보실 수 있습니다. 생긴게 좀 복잡하긴 한데요, 이걸 뭐 직접 짜야하는건 아니고 컴파일러가 작업해주는 거니까요. 이 이야기는 나중에 더 자세하게 다루도록 하구요. 일단 dynamic이 어디에 어떤 모습으로 쓰일 수 있고, 그게 뭘 의미하는지 더 알아보도록 하겠습니다. 우선, 아래의 코드를 보시져. 

dynamic d = ...;

d.Foo(1, 2, 3); // (1)

d.Prop = 10; // (2)

var x = d + 10; // (3)

int y = d; // (4)

string y = (string)d; // (5)

Console.WriteLine(d); // (6)
(d.Foo(); 에서 d는 Foo의 실행요청을 받는 receiver이고, d의 타입이 dynamic이라면 d는 dynamic receiver가 되는거임!)

1. Foo메서드의 호출요청을 받은 객체의 타입이 dynamic이므로 컴파일러는 런타임에게 이 코드에서 d의 실제 runtime type이 뭐든지에 상관없이 "Foo"라는 메서드를 매개변수{1, 2, 3}를 적용해서 바인드해야 한다는걸 알려줍니다.

2. 역시 1번과 마찬가지로 dynamic receiver가 있으므로 컴파일러는 런타임에게 이 코드에서는 "Prop"이라는 프로퍼티비스무리한(필드나 프로퍼티)걸 바인드해야 하고 거기에 10이라는 값을 할당해야 한다고 알려줍니다.

3. 여기서는 +연산자는 동적으로 바인드되는 연산인데요, 매개변수중에 하나가 dynamic이기 때문이죠. 런타임은 실제 d의 runtime type에 대해서 일반적인 연산자 오버로딩 규칙을 따라서 적합한 연산을 찾습니다.

4. 여기서는 암시적인 형변환이 있는데요, 컴파일러는 int와 d의 runtime type에 대한 모든 형변환을 고려해본뒤에 d에서 int로의 형변환이 가능한지 판단하도록 런타임에게 알려줍니다.

5. 이번에는 명시적인 형변환 인데요, 컴파일러는 이 변환을 컴파일하고 런타임에게 이 형변환에대해서 검토해보도록 알려줍니다.

6. 비록 컴파일타임에서 볼 수 있는 메서드를 호출하지만, 인자가 dynamic이므로 컴파일타임에서는 오버로딩 판별을 할 수 없습니다. 그래서 어떤 Console.WriteLine을 호출할지도 역시 런타임에 결정하게 됩니다.


- 마치면서

오늘은 dynamic타입에 대해서 이야기 해봤습니다. 재주가 부족해서 잘 설명한거 같지 않네요;;; 생각보다 dynamic배후의 이야기가 많은데요, 다음시간부터 거기에 대해서 하나씩 하나씩 이야기 해보겠습니다~.


- 참고자료

1. http://blogs.msdn.com/samng/archive/2008/10/29/dynamic-in-c.aspx
2. http://blogs.msdn.com/cburrows/archive/2008/10/27/c-dynamic.aspx
3. http://channel9.msdn.com/pdc2008/TL10/

[JumpToDX11-2]DeviceContext...넌 누구냣!!

DirectX 11 2009. 8. 24. 14:00 Posted by 알 수 없는 사용자



지난 회에서 DXGI 에 대해서 잠깐 살펴보았습니다.
DXGI 에 대한 사용방법은 여기서 언급하지 않습니다.
왜냐하면 제가 진행할 코딩에서는 기본적으로 셋팅되어 있는 그래픽 카드를 사용하는 것을 전제로
진행할 것이기 때문입니다.

혹시 호기심이 더 왕성하신 분들은 IDXGIFactory 인터페이스를 살펴보시면 됩니다.
멤버 함수중에 MakeWindowAssociation, EnumAdapters 정도 살펴보시면 도움이 될 것입니다.
( API 함수 이름만 봐도 느낌이 팍팍! )

예상하시겠지만, DXGI는 연결가능한 장치(어댑터)들을 나열해서
그것 중에 하나 선택하는 역활이 필요합니다.
저는 기본적으로 설정된 어댑터를 사용할 것이기 때문에 이들에 대해서는 언급하지 않겠습니다.
 DXGI 계열의 API들도 양이 상당합니다.( 그래서 일단 패스~ )


지난 회에 언급했던 디바이스 초기화를 위한 변수들 기억하시나요?
다시 나열해 보면 아래와 같습니다.




< IDXGISwapChain >

가장 먼저  IDXGISwapChain 에 대해서 살펴봐야하겠지만, 이것에 대한 별도의 설명이 필요할까요?
Front Buffer( 현재 화면에 보여지는 버퍼 )와 Back Buffer( 현재 연산을 해서 기록하는 버퍼 ) 를
준비해서 이것을 Flip 시키면서 번갈아 가면서 보여주는 것을 의미합니다.
( 너무 고전적인 내용이라 더 설명하면 혼날듯...)

우리가 렌더링할 영역(버퍼)에 대한 포맷과 같은 각종 속성들을 설정해 주어서 생성을 요구하고,
포인터를 받으면, 이들 버퍼에 대한 정보를 제어
할 수 있습니다.
나중에 살펴보게 되겠지만, Present() 라는 API 를 기억하시나요?
9.0 에서는 이것이 ID3DDevice9의 멤버함수로써 사용했었습니다.
하지만 현재는 IDXGISwapChain 의 멤버함수로 등록되어 잇습니다.
그래서 이에 대한 포인터가 필요합니다.^^
결론적으로 얘기 드리면 앞으로 화면 출력에 관한 모든 것은 IDXGI 계열의 인터페이스로서 제어할 수 있습니다.





아마 위와 같은 형식이겠죠? ( 각각의 성분에 대한 설명은 생략합니다...저걸 다 어찌 설명해요..-_- )

 

 

 < DeviceContext...넌 누구냣!! >

이상한 인터페이스가 DirectX11 에서 생겼습니다.
Device는 무엇인지 알겠는데, DeviceContext 는 또 무엇일까요?
사실 이것은 그동안 Device 인터페이스들이 해오던 역활을 두가지로 분리한 것에 지나지 않습니다.

즉, ID3D11Deivce 는 주로 리소스( 버퍼나 텍스쳐 등 )의 생성에 대한 인터페이스이며,
ID3D11DeviceContext 는  이들 리소스를 제어하고 관리하기 위한 인터페이스입니다.

그렇다면 왜 이렇게 두 가지로 분리된 것일까요?
먼저 아래의 그림을 살펴보겠습니다.




우리가 렌더링을 수행하기 위해서는 애플리케이션에서는 관련 Core API 와 Runtime을 사용하게 됩니다.
이들 Core API 와 Runtime 은 우리가 필요한 렌더링에 관한 모든 것을 수행합니다.
메모리 할당이나 리소스들의 수정, 메모리 바인딩, 각종 렌더링 스테이트의 제어 등등 굉장히 많죠.
( 물론 쉐이더 코드들을 통해서도 이들을 제어할 수 있는 부분이 있습니다만,
  여기서는 흐름상 고려하지는 않습니다.  ) 

DirectX 시스템은 Application 과의 오버헤드를 최소화 하기 위해서
이들 사이를 매우 얇은 추상화 단계로 디자인 했었습니다.
즉, Core API 나 Runtime 들은 바로 Driver 에 접근할 수 있었습니다.

그래도 약간(?) 존재해 있는 Application 과 하드웨어간의 오버헤드를 줄이기 위해서
기존의 Device 의 역활을 Device 와 DeviceContext 로 분리
하게 된 것입니다. 

그렇다면 여기서 발생되는 오버헤드란 것은 어떤 것일까요?( 의문에 의문 연속입니다..-_- )
우리가 사용하는 각종 API 들은 Runtime 에 전달되어서 하드웨어가 인식할 수 있는
커맨드( Command ) 들로 변환
됩니다.

Runtime 은 이들 커맨드들을 담을 수 있는 메모리 공간을 가지고 있어서, 커맨드들을 저장하게 됩니다.
그러다가 이들 버퍼가 가득차거나, 혹은 렌더링 데이터의 업데이트가 필요한 경우에
이들을 하드웨어로 전송하게 되는 것입니다.
바로 이 커맨드들에 대해서 오버헤드가 발생하는 것입니다.
이 커맨드들이 오버헤드를 발생시키는 이유는 여러가지가 있었습니다.
하드웨어의 경우에는 프로세싱( processing ) 스타일이 매우 다양하기도 했고,
API 와 하드웨어 상에서 커맨드 전달이 잘못 전달되는 경우도 있었다고 합니다.
( 아무래도 하드웨어가 너무 다양해서가 주된 이유였던 듯 합니다. )

이들에 대한 오버헤드를 줄이는 방법을 고민하던 중에 나온 결과물 중에 하나가
바로 'DeviceContext' 라는 것입니다.
( 뒤에 언급할 기회가 있겠지만, 'State Object' 가 바로 이 오버헤드를 줄이기 위해 등장한 개념이기도 합니다. )

Device 의 경우에는 오버헤드를 줄이기 위해 등장한 개념이
리소스의 생성/해제를 담당하는 커맨드들과 그 리소스들을 제어하는 커맨드들로 분리하는 것입니다.

 

분리함으로써 어떤 성능 향상이 있었을까요?
리소스의 생성과 해제는 사실 멀티스레드 형태의 API 호출에도 별 문제가 없습니다.
어차피 명령어들이 큐 형태로 쌓이게 될테니까요.
반면에 렌더링 커맨드들은 멀티스레드 형식으로 구성되면 큰일 나겠죠?

결국 Device 는 Free threaded 형식으로 구성되었고,
DeviceContext 는 그렇지 않다는 것
입니다.
Free threaded 형식으로 구성되었다는 것은 스레드에 안정성을 유지하기 위한
별도의 lock/unlock 작업이 필요없다는 것입니다.
멀티스레드에 안정적이라는 얘기는 스레드 세이프하다는 것입니다.

(정확하게 확신은 아직 드릴 수 없지만, 멀티스레드 관련 렌더링과도 관련이 있는 부분이 여기이지 않을까요.)

사실 리소스의 생성과 해제가 성능에 많은 부분을 차지한다고 볼때,
이렇게 분리되어진 것을 환영해야 할 것입니다.

 

 < 다음 회에는... >

글이 좀 길어지는 것 같아서 일단 여기서 마무리 합니다.
다음 회에는 나머지 초기화 부분에 대해서 계속 언급하겠습니다.^^

 





 

'DirectX 11' 카테고리의 다른 글

[DX11_#2]D3D Buffer( 2 / 2 )  (0) 2009.10.13
[DX11_#1]D3D Buffer( 1 / 2 )  (0) 2009.09.22
[JumpToDX11-4] ID3D11View  (0) 2009.09.07
[JumpToDX11-3] Feature Level  (0) 2009.08.31
[JumpToDX11-1] 사라진 Direct3D 오브젝트를 찾아서...  (8) 2009.08.17

Welcome to Dynamic C#(4) - 극과극 비교체험.

C# 2009. 8. 20. 20:25 Posted by 알 수 없는 사용자

- 또 쓸데없는 생각 하냐?

안녕하세요. 정말 오랜만입니다. 사연이 많은 사람이다 보니, 잠수를 자주 타게 되네열. -_-;;;;; 그래서 뭐라도 써야한다는 생각을 하다가, 별로 쓸모있을진 모르겠지만, 실행속도를 비교해보자는 생각이 들었습니다. 짧은 글이 되겠지만, 조금이라도 도움이 되길바라면숴!


- 빨랑 비교한거 내놔.

비교대상은 한 클래스에 있는 메서드를 그냥 호출하는 것과 dynamic을 통해 호출하는 것, 그리고 리플렉션을 통해서 호출하는 세가지방법입니다. 그리고 각 호출을 메서드를 10만, 50만, 100만, 300만, 500만번 호출하는 것으로 속도를 재어봤습니다. 실행의 대상이 된 코드는 아래와 같습니다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace ConsoleApplication2
{
    class Test
    {
        public int FivePlusFive()
        {
            return 10;
        }
    }

    class Program
    {
        public void ReflectionCall()
        {
            object test = new Test();
            Type type = test.GetType();
            type.InvokeMember("FivePlusFive", System.Reflection.BindingFlags.InvokeMethod, (Binder)null,
                test, new object[] {});
        }

        public void DynamicCall()
        {
            dynamic test = new Test();
            test.FivePlusFive();
        }

        public void NormalCall()
        {
            Test test = new Test();
            test.FivePlusFive();
        }

        static void Main(string[] args)
        {
            Program prog = new Program();

            //for JIT compile
            prog.ReflectionCall();
            prog.DynamicCall();
            prog.NormalCall();

            long limit = 5000000;

            DateTime normalStart = DateTime.Now;
            for (int i = 0; i < limit; i++)
            {
                prog.NormalCall();
            }
            DateTime normalEnd = DateTime.Now;
            TimeSpan normalResult = normalEnd - normalStart;

            DateTime dynamicStart = DateTime.Now;
            for (int i = 0; i < limit; i++)
            {
                prog.DynamicCall();
            }
            DateTime dynamicEnd = DateTime.Now;
            TimeSpan dynamicResult = dynamicEnd - dynamicStart;

            DateTime reflectionStart = DateTime.Now;
            for (int i = 0; i < limit; i++)
            {
                prog.ReflectionCall();
            }
            DateTime reflectionEnd = DateTime.Now;
            TimeSpan reflectionResult = reflectionEnd - reflectionStart;

            Console.WriteLine("Normal Time : {0}", normalResult);
            Console.WriteLine("Dynamic Time : {0}", dynamicResult);
            Console.WriteLine("Reflection Time : {0}", reflectionResult);
        }
    }
}



JIT컴파일에 걸리는 시간을 빼기 위해서 일단 한번씩 먼저 실행했구요, 각각의 방법을 정해진 횟수만큼 실행해서 시간을 측정하는 방식으로 했습니다. 그럼 결과를 보시져!!!! 야호!!!! 완전 신나!!!! -_-......

- 10만번

- 50만번

- 100만번

- 300만번

- 500만번


그리고 위의 결과를 표로 종합해보면 아래와 같습니다.


일반 호출은 리플렉션에 비해서 너무 작아서 그런지 아예 나타나지도 않는군요-_-;;;; 별로의미있는 코드로 실험을 한건 아니지만, 다이나믹이 리플렉션에 비해서는 월등히 빠르군요. 아마도 DLR의 힘을 빌려서 리플렉션 보다 훨씬 빠른 방식을 이용하는 것 같습니다. 이 부분에 대해서는 좀 조사를 해봐야 할거 같네요.


- 피드백 및 정정사항!!ㅋ

이 글을 보시고 정성태님께서 피드백을 주셨습니다.(http://www.sysnet.pe.kr/Default.aspx?mode=2&sub=0&detail=1&pageno=0&wid=766&rssMode=1&wtype=0) 정성태님의 블로그를 들르면서 내공의 깊이에 감탄을 하곤했는데, 직접 피드백을 받으니 더 확실하네 느껴지네요^^ㅋ. 글의 내용을 보시면, 제가 단순히 리플렉션 호출을 반복하게 설정해놓은 것에서 리플렉션에 매우 불리한 결과가 도출되는 요인이 있음을 지적하시고, 더 빠르게 그리고 오히려 다이나믹 보다도 더 빠른 결과가 나올 수 있는 방식을 제시해주고 계십니다.

잘 몰랐던 부분에 대해서 지적해주셔서 좋은거 배웠네요~.


- 마치면서

별로 내용도 없는 글을 썼군요-_-;;; 다음 포스트부터는 dynamic에 대해서 좀 더 심도 깊게 파보려고 생각중입니다. 좋은글이 많은데 잠수타고 정신줄 놓느라고 못보고 있었더군요!! 암튼. 곧 돌아오겠슴돠. ㅋㅋㅋ

Parallel Patterns Library(PPL) - 병렬 알고리즘

VC++ 10 Concurrency Runtime 2009. 8. 19. 13:00 Posted by 알 수 없는 사용자

Parallel Patterns Library(이하 PPL)에는 데이터 컬렉션을 대상으로 쉽게 병렬 작업을 할 수 있게 해 주는 알고리즘이 있습니다. 이 알고리즘들은 생소한 것들이 아니고 C++의 표준 템플릿 라이브러리(STL)에서 제공하는 알고리즘과 비슷한 모양과 사용법을 가지고 있습니다.

( *데이터 컬렉션은 데이터 모음으로 배열이나 STL 컨테이너를 생각하면 됩니다 )

 

 

PPL에서 제공하는 병렬 알고리즘은 총 세 개가 있습니다.

 

1. parallel_for        알고리즘

2. parallel_for_each 알고리즘

3. parallel_invoke    알고리즘

 

 

세 개의 알고리즘 중 3 parallel_invoke만 생소하지 1번과 2번은 앞의 ‘parallel_’이라는 글자만 빼면 ‘for’‘for_each’ C++로 프로그래밍할 때 자주 사용하는 것이므로 친숙하게 느껴질 겁니다.

실제 병렬 여부만 제외하면 우리가 알고 있는 것들과 비슷한 동작을 합니다. 그래서 쉽게 배울 수 있고 기존의 코드에 적용하기도 쉽습니다.

 


parallel_for 알고리즘은 일반적인 for문을 사용할 때와 비슷하게 데이터 컬렉션에서 시작할 위치와 마지막 위치, 증가분(생략 가능합니다)에 해야할 작업 함수를 파라미터로 넘기면 됩니다. 사용 방법에서 for문과 다른 점은 작업 함수를 넘긴다는 점입니다.

 

parallel_for_each 알고리즘은 기존 for_each와 거의 같습니다. 데이터 컬렉션에서 시작할 위치, 마지막 위치, 작업 함수를 파라미터로 넘기면 됩니다. parallel_for의 경우 기존의 for문을 사용할 때는 작업 함수를 파라미터로 넘기지 않기 때문에 기존 for 문에 비해서 구조가 달라지지만 parallel_for_each는 기존 for_each와 파라미터 사용 방법이 같기 때문에 알고리즘의 이름만 바꾸면 될 정도입니다.

 

parallel_invoke 알고리즘 이전 회에 설명한 태스크 그룹과 비슷한면이 있습니다. 태스크 그룹과의 큰 차이점은 병렬로 할수 있는 작업은 10개로 제한 되지만 사용 방법은 태스크 그룹보다 더 간결한 점입니다다. 병렬 작업의 개수가 10개 이하인 경우 태스크 그룹보다 parallel_invoke를 사용하는 것이 훨씬 더 적합하다고 생각합니다.

 

 

 

 

이번은 간단하게 PPL에 있는 세 가지 병렬 알고리즘을 소개하는 것으로 마칩니다. 다음 회부터는 이번에 소개했던 세 개의 알고리즘을 하나씩 하나씩 자세하게 설명하겠습니다.

Parallel Patterns Library(PPL) - Task

VC++ 10 Concurrency Runtime 2009. 8. 18. 00:27 Posted by 알 수 없는 사용자
이번 글은 길이가 좀 깁니다. 내용은 복잡한 것이 아니니 길다고 중간에 포기하지 마시고 쭉 읽어주세요^^


이전 회에서는 PPL에 대한 개념을 간단하게 설명했고, 이번에는 PPL의 세가지 feature 중 태스크(Task)에 대해서 설명하려고 합니다. 태스크에 대한 설명은 이미 이전에 정재원님께서 블로그를 통해서 설명한 적이 있습니다. 정재원님의 글은 태스크 사용 예제 코드를 중심으로 설명한 것으로 저는 그 글에서 빠진 부분과 기초적인 부분을 좀 더 설명하려고 합니다.

 

태스크라는 것은 작업 단위라고 생각하면 좋을 것 같습니다. 작업이라는 것은 여러 가지가 될 수 있습니다. 피보나치 수 계산, 배열에 있는 숫자 더하기, 그림 파일 크기 변경 등 작고 큰 작업이 있습니다. 보통 크기가 큰 작업은 이것을 작은 작업 단위로 나누어 병렬 처리를 하기도 합니다.

 

PPL의 태스크는 작업을 그룹 단위로 묶어서 병렬로 처리하고 대기 및 취소를 할 수 있습니다.

 

 


태스크 핸들

태스크 핸들은 각각의 태스크 항목을 가리키며 PPL에서는 task_handle 클래스를 사용합니다. 이 클래스는 람다 함수 또는 함수 오브젝트 등을 태스크를 실행하는 코드로 캡슐화 합니다. 태스크 핸들은 캡슐화 된 태스크 함수의 유효 기간을 관리하기 때문에 중요합니다. 예를들면 태스크 그룹에 태스크 핸들을 넘길 때는 태스크 그룹이 완료 될때까지 유효해야합니다.


보통 태스크 관련 예제 코드를 보면 task_handle 대신 C++0x의 auto를 사용하는 편이 코드가 더 간결해지므로 task_handle 보다는 auto를 사용하고 있습니다.


 

 

unstructured structured Task Groups

태스크 그룹은 unstructured structured 두 개로 나누어집니다.

두개의 태스크 그룹의 차이는 스레드 세이프하냐 안하느냐의 차이입니다.

unstructured는 스레드 세이프 하고 structured는 스레드 세이프 하지 않습니다.


태스크 관련 예제에 자주 나오는 task_group 클래스는 unstructured 태스크 그룹이고, structured_task_group 클래스는 structured 태스크 그룹을 뜻합니다.

 

unstructured 태스크 그룹은 structured 태스크 그룹보다 유연합니다. 스레드 세이프 하며 작업 중 taks_group::wait를 호출하여 대기한 후 태스크를 추가한 후 실행할 수 있습니다. 그렇지만 성능면에서 structured 태스크 그룹이 스레드 세이프 하지 않으므로 unstructured 태스크 그룹보다 훨씬 더 좋으므로 적절하게 선택해서 사용해야 합니다.

 

structured 작업 그룹은 스레드 세이프 하지 않기 때문에 Concurrency Runtime에서는 몇가지 제한이 있습니다.

- structured 작업 그룹 안에 다른 structured 작업 그룹이 있을 경우 내부의 작업 그룹은 외부의 작업 그룹보다 먼저 완료해야 한다.

- structured_task_group::wait 멤버를 호출한 후에는 다른 작업을 추가한 후 실행할 수 없다.


 

 

초간단!!! 6단계로 끝내는 태스크 사용 방법


1. ppl.h 파일을 포함합니다.

   #include <ppl.h>

 

2. Concurrency Runtime의 네임 스페이를 선언합니다.

   using namespace Concurrency;

 

3. 태스크 그룹을 정의합니다.

  structured_task_group structured_tasks;

 

4. 태스크를 정의합니다.

  auto structured_task1 = make_task([&] { Plus(arraynum1, true); } );

 

5. 태스크를 태스크 그룹에 추가한 후 실행합니다.

  structured_tasks.run( structured_task1 );

 

6. 태스크 그룹에 있는 태스크가 완료될 때까지 기다립니다.

  structured_tasks.wait();

 

위의 순서대로 하면 태스크를 사용할 수 있습니다. 태스크 사용 참 쉽죠잉~ ^^.

참고로 여러 개의 태스크를 그룹에 추가하고 싶다면 6번 이전에 4번과 5번을 추가할 개수만큼 반복하면 됩니다.


* 4번의 Plus(arraynum1, true);는 하나의 태스크에서 실행할 함수입니다.

 


PPL의 태스크를 사용하면 병렬 프로그래밍을 간단한 6단계만으로 끝낼 수 있습니다. 만약 현재의 Win32 API로 이것을 구현하기 위해서는 학습에 많은 시간을 보낸 후 저수준의 API를 사용하여 구현해야 되기 때문에 구현 시간과 안정성에서 PPL의 태스크보다 손해를 봅니다.




태스크 그룹과 스레드 세이프

unstructured structured 태스크 그룹의 차이가 스레드 세이프 유무의 차이라고 했는데 이 말은

unstructured 태스크 그룹은 복수의 스레드에서 호출 및 대기를 할 수 있지만 structured 태스크 그룹은 그것을 생성한 스레드에서만 호출 및 대기를 할 수 있습니다.


예를 들면 스레드 A, 스레드 B가 있는 경우 스레드 A와 B에서 태스크를 실행 후 대기를 한다면 unstructured 태스크 그룹을 사용해야하고, 오직 하나의 스레드에서만(스레드 A에서만) 태스크를 실행 후 대기를 한다면 structured 태스크 그룹을 사용합니다.


스레드 세이프는 스레드 세이프 하지 않는 것보다 오버헤드가 발생합니다. 즉 스레드 세이프 버전은 스레드 세이프 하지 않은 버전보다 성능이 떨어진다는 것이죠.

그러니 태스크 그룹을 어떤 방식으로 사용할지 파악 후 스레드 세이프 필요성에 따라서 unstructured 태스크 그룹과 structured 태스크 그룹 중 상황에 알맞은 것을 선택해서 사용해야 합니다.




ps : 제가 8월 14일 글을 공개할 때 태스크 그룹의 스레드 세이프 특성을 잘 못 이해하여 잘못된 내용을 전달하였습니다. 그래서 오늘 글을 다시 수정하였습니다. ;;;;;;

다음부터는 틀린 글을 올리지 않도록 조심하겠습니다. ^^;;;;;;

[JumpToDX11-1] 사라진 Direct3D 오브젝트를 찾아서...

DirectX 11 2009. 8. 17. 14:00 Posted by 알 수 없는 사용자

< 인사 및 소개 >

안녕하세요.
저는 이번에 vsts2010 에 참여하게 된 조진현 이라고 합니다.

어떤 주제에 대해서 글을 쓰다는 것은 무척 어려운 일입니다.

그렇기 때문에, 이 스터디 참가를 굉장히 망설이기도 했습니다.
많은 분들과 함께 열정을 가지고 참가를 결심했고, 드디어 처음으로 글을 남기게 되었습니다.
제가 가장 우려하는 것은 잘못된 지식을 전달하는 것입니다.
그래서 조심스러운 마음으로 글을 작성할 것입니다.
잘못된 부분이나 미흡한 부분이 있으면, 바로 지적해주시면 감사하겠습니다.

제가 언급할 큰 주제는 DirectX 11 과 관련이 있습니다.
그 중에서도 멀티 코어를 활용한 DirectX 사용에 초점을 두고 글을 전개할 생각입니다.
글의 주요 대상은 DirectX9 를 사용하시다가 DirectX11 을 사용하고자 하시는 분들입니다.

일단 방대한 변화에 대해서 모두 나열하기는 힘듭니다.

그래서 간단히 제가 코딩을 하면서 필요했던 API 위주로 살펴보면서 변화를 언급하고자 합니다.
그런데 하나 문제가 있습니다.
현재 DirectX 11 은 하드웨어 가속이 지원되지 않습니다.
오직 REF 모드로만 작동을 합니다.
아마도 아직 정식으로 widnows 7 이 출시가 이루어지지 않아서 그런 듯 합니다.
이점, 꼭 주의하시기 바랍니다.
괜히 DirectX 11 예제 실행했다가, 실행 성능이 떨어진다고 컴퓨터를 부수는 행위는 자제해 주세요.^^


< 사라진 Direct3D 오브젝트를 찾아서... >

우리가 가장 먼저 접하게 되는 DirectX 의 API 는 CreateDevice() 일 것입니다.
사실 이전 버전까지는 CreateDevice() 에 대해서 별도로 언급할 내용이 없었을 것이지만,
늘(?) 그렇듯이 DirectX 의 변화를 설명해주는 API 가 바로 CreateDevice() 입니다.
일단 CreateDevice() 를 위한 관련 변수들부터 봐야겠죠?
 



잠깐!!
가장 먼저 헤더 파일들을 살펴보는게 순서이죠.

헤더는 다음과 같이 변경되었습니다.
굳이 헤더의 용도에 대해서 일일이 나열하지는 않았습니다.

// Direct3D11 includes
#include <dxgi.h>
#include <d3d11.h>
#include <d3dCompiler.h>
#include <d3dx11.h>
#include <dxerr.h>

 

라이브러리 링크는 아래의 것들을 해주시면 됩니다.

#pragma comment( lib, "dxguid.lib" )
#pragma comment( lib, "d3dcompiler.lib" )
#pragma comment( lib, "dxerr.lib" )
#pragma comment( lib, "dxgi.lib" )
#pragma comment( lib, "d3d11.lib" )
#pragma comment( lib, "d3dx11.lib" )




변수들을 나열해 보겠습니다.




 생소한 부분이 눈에 보이시나요?
 'ID3D11DeviceContext' 라는 것이 새롭게 등장했습니다. ( 다음 번에 언급할 것입니다. )
 그리고 Direct3D 인터페이스가 사라진 것을 찾으셨습니까?




위의 그림은 DirectX 9 의 아키텍쳐입니다.
우리가 작성하는 프로그램은 오직 Direct3D 나 GDI 를 통해서 저수준의 하드웨어와 통신을 할 수 있었습니다.

그런데 현재의 DirectX 아키텍쳐는 아래와 같습니다.



 여기서 또 하나 생소한 것이 등장했습니다.
바로 DXGI ( DirectX Graphics Infrastructure ) 입니다.
"DirectX9 에서 사라진 'Direct3D 오브젝트'를 'DXGI' 가 대체하는게 아닐까?" 라는 의문이 들었다면,
박수를 보내드리고 싶습니다.( 브라보~~ )


네, 맞습니다.
'DXGI' 라는 것이 바로 사라진 'Direct3D 오브젝트' 입니다.
'Direct3D 오브젝트' 의 역활에 대해서 혹시 기억하십니까?
하드웨어와 연결된 디바이스들을 나열하고, 모니터로 출력되는 결과들을 관리해주기도 했었습니다.
우리가 관리하기 힘든 저 수준의 작업들을 바로 이 'Direct3D 오브젝트'가 했었습니다.
그런데 이제는 이것을 'DXGI' 가 해주고 있습니다.
( IDXGISwapChain 보이시나요? 이것도 다음 회에 언급하겠습니다. )
 

아키텍쳐 구조를 보시면 아시겠지만, DirectX9 까지는 일반 애플리케이션에서 DirectX API 를 통하지 않고는
DirectX 를 사용할 수 없었습니다.
그런데 최근에는 일반 애플리케이션은 모두 DXGI 를 통해서 DirectX 를 사용하고 있습니다.
( 저만 놀라운 것은 아니겠죠? +_+ )
마이크로소프트에서도 강조하고 있는 사실 중에 하나가 바로 DirectX 는 더 이상 게임만을 위한 것이 아니라는 것입니다.
이제 사라진 줄 알았던 'Direct3D 오브젝트' 가 DXGI 라는 사실을 알았습니다.
앞으로 저수준의 작업이 필요하면 DXGI 를 직접 제어하거나 DirectX API 를 이용하셔도 됩니다.


< 다음 회에는... >

다음 번에는 실제로 DirectX API 를 이용한 초기화 작업에 대해서 다루고자 합니다.
즉, 우리가 앞서 선언했던 변수들에 대한 이야기를 하겠습니다.

'DirectX 11' 카테고리의 다른 글

[DX11_#2]D3D Buffer( 2 / 2 )  (0) 2009.10.13
[DX11_#1]D3D Buffer( 1 / 2 )  (0) 2009.09.22
[JumpToDX11-4] ID3D11View  (0) 2009.09.07
[JumpToDX11-3] Feature Level  (0) 2009.08.31
[JumpToDX11-2]DeviceContext...넌 누구냣!!  (1) 2009.08.24

양보할 줄 아는 Concurrency Runtime의 event

VC++ 10 Concurrency Runtime 2009. 8. 7. 18:48 Posted by 알 수 없는 사용자
병행 런타임 관련 두번째 예제로 event를 이용합니다. 여기서 이벤트는 뮤텍스 등과 같은 동기화 개체의 하나로 기존 Win32에서의 수동리셋이벤트(manual-reset event)와 같은 것을 말합니다.

기존 이벤트와 병행 런타임에서 제공하는 이벤트에는 한가지 중요한 차이점이 있습니다. 새로운 이벤트는 병행 런타임을 인식하여 작업이 블록되는 경우 다른 작업에 스레드를 양보(yield)합니다. 무조건 선점형으로 동작하는 기존 Win32 이벤트보다 더 지능적이고 효율적으로 동작하는 것이죠.

자, 그럼 코드를 살펴봅시다. 이 예제에서는 스케줄러의 병렬성을 2로 제한하고 그보다 많은 수의 작업을 병행 수행할 때, 기존 Win32 이벤트와 병행 런타임 이벤트를 각각 활용하는 경우 어떤 차이가 있는지 보여줍니다.

    1 // event.cpp : Defines the entry point for the console application.

    2 //

    3 // compile with: /EHsc

    4 #include <windows.h>

    5 #include <concrt.h>

    6 #include <concrtrm.h>

    7 #include <ppl.h>

    8 

    9 using namespace Concurrency;

   10 using namespace std;

   11 

   12 class WindowsEvent

   13 {

   14     HANDLE m_event;

   15 public:

   16     WindowsEvent()

   17         :m_event(CreateEvent(NULL,TRUE,FALSE,TEXT("WindowsEvent")))

   18     {

   19     }

   20 

   21     ~WindowsEvent()

   22     {

   23         CloseHandle(m_event);

   24     }

   25 

   26     void set()

   27     {

   28         SetEvent(m_event);

   29     }

   30 

   31     void wait(int count = INFINITE)

   32     {

   33         WaitForSingleObject(m_event,count);

   34     }

   35 };

   36 

   37 template<class EventClass>

   38 void DemoEvent()

   39 {

   40     EventClass e;

   41     volatile long taskCtr = 0;

   42 

   43     //태스크그룹을 생성하고 여러 태스크 사본을 스케줄링합니다.

   44     task_group tg;

   45     for(int i = 1;i <= 8; ++i)

   46         tg.run([&e,&taskCtr]{           

   47 

   48       //작업 부하를 시뮬레이션합니다.

   49             Sleep(100);

   50 

   51             //태스크 카운터를 증가시킵니다.

   52             long taskId = InterlockedIncrement(&taskCtr);

   53             printf_s("\tTask %d waiting for the event\n", taskId);

   54 

   55             e.wait();

   56 

   57             printf_s("\tTask %d has received the event\n", taskId);

   58 

   59     });

   60 

   61     //이벤트를 셋하기 전에 충분히 시간을 보냅니다.

   62     Sleep(1500);

   63 

   64     printf_s("\n\tSetting the event\n");

   65 

   66     //이벤트를 셋

   67     e.set();

   68 

   69     //작업들의 완료를 대기

   70     tg.wait();

   71 }

   72 

   73 int main ()

   74 {

   75     //스레드 둘만을 활용하는 스케줄러를 생성합니다.

   76     CurrentScheduler::Create(SchedulerPolicy(2, MinConcurrency, 2, MaxConcurrency, 2));

   77 

   78     //협력적 이벤트를 사용할 경우, 모든 작업들이 시작됩니다.

   79     printf_s("Cooperative Event\n");

   80     DemoEvent<event>();

   81 

   82     //기존 이벤트를 사용하면, Win7 x64 환경이 아닌한

   83     //ConcRT가 블록 상황을 인식하지 못하여 첫 두 작업만이 시작됩니다.

   84     printf_s("Windows Event\n");

   85     DemoEvent<WindowsEvent>();

   86 

   87     return 0;

   88 }


WindowsEvent 클래스는 기존 Win32 이벤트를 위한 랩퍼(wrapper) 클래스입니다. DemoEvent 함수 템플릿이 사용할 이벤트 형을 템플릿 인자로 받아 실제 작업을 하는 놈입니다. PPL(Parallel Patterns Library)의 task를 이용해 8개 작업을 만들고 각각에서 이벤트를 기다리도록 하고 있습니다.

결과는 다음과 같이 나올 겁니다.


병렬성이 둘로 제한되는 상황에서도 병행 런타임의 이벤트를 사용할 경우 각 작업이 블록될 경우 다른 작업에 스레드를 양보하기 때문에 8개의 작업이 모두 시작되는 것을 확인하실 수 있습니다. 반면, 기존 이벤트의 경우 두 작업만이 시작되었다가 이벤트를 받고 두 작업이 종료된 후에나 다른 작업들이 시작되는 것을 확인하실 수 있습니다.

Parallel Patterns Library (PPL)

VC++ 10 Concurrency Runtime 2009. 8. 6. 06:00 Posted by 알 수 없는 사용자

이제 본격적으로 VC++ 10의 병렬 프로그래밍에 대한 이야기를 시작합니다.

첫 번째는 이름만 들어도 딱 '병렬 프로그래밍' 이라는느낌을 주고 가장 많이 사용될 것으로 생각하는 Parallel Patterns Library (PPL)입니다정말 이름에서 딱 느낌이 오죠 ^^



PPL은 크게 세 개의 features로 나누어집니다.

1. Task Parallelism : 병렬적으로 여러 가지 작업 처리

2. Parallel algorithms : 데이터 컬렉션을 제너릭 알고리즘으로병렬 처리

3. Parallel containers and objects :concurrent 접근이 가능한 제너릭 컨테이너

 


PPL 모델은 C++의 Standard Template Library(STL)과비슷합니다.

예를 들면 STL에는 for_each 라는 것이 있는데 PPL에는 이것의 병렬 버전인 parallel_for_each가 있습니다. 뒤에 설명하겠지만 parallel_for_each에 대해서 간단하게 말하면 array의 항목을 순회하는 parallel 알고리즘입니다.



PPL을 사용하기 위해서는 먼저 namespace Concurrency를 선언한 후 ppl.h 파일을 포함합니다.
........
#include <ppl.h>

using namespace Concurrency;
..............


먼저 parallel_for_each를 사용한 코드를 보여 드리겠습니다. parallel_for_each는 다음에 자세히 설명하겠으니 이번은 PPL 이라는 것이 어떻게 사용하는지만 아래 코드를 통해서 보세요^^

< 리스트 1. parallel_for_each 예제 >

#include <ppl.h>

#include <array>

#include <algorithm>

 

using namespace std;

using namespace std::tr1;

using namespace Concurrency;

 

int main()

{

   // Create anarray object that contains a few elements.

   array<int, 3> a = {13, 26, 39};

 

   // Use thefor_each algorithm to perform an operation on each element

   // of the arrayserially.

  for_each(a.begin(), a.end(), [&](int n) {

      // TODO:Perform some operation on n.

   });

 

   // Use theparallel_for_each algorithm to perform the same operation

   // in parallel.

  parallel_for_each(a.begin(), a.end(), [&](int n) {

      // TODO:Perform some operation on n.

   });

}


<리스트 1>의 코드를 보면 람다를 사용한 부분도 보이죠? 예전에 제가 C++0x의 새로운 기능에 의해 C++의 성능과 표현력이 향상 되었다고 이야기 했습니다. 이런 장점들이 PPL에 많은 기여를 하였습니다.




PPL과 OpenMP

예전에 PPL이 MSDN 매거진을 통해서 공개 되었을 때 많은 분들이 OpenMP와 비슷하게 보시고 왜 기존에 있는 것과 같은 것을 또 만드냐 라는 이야기를 하는 것을 들은 적이 있습니다.

PPL과 OpenMP는 같은 것이 아닙니다. 표현 방법이 얼핏 비슷하게 보일지 몰라도 개념이나 기반은 많이 다릅니다.

OpenMP는 pragma 지신문이고 PPL은 순수 C++ 템플릿으로 만들어진 라이브러리입니다.
그래서 PPL은 표현성과 유연성이 OpenMP에서 비해서 훨씬 더 뛰어납니다.
또한 PPL은 Concurrency Runtime 기반 위에 구축되므로 동일한 런타임을 기반으로 하는 다른 라이브러리와 잠재적 상호 운용성이 제공됩니다.

PPL은 어떤 것인지, 왜 OpenMP 보다 더 좋은지 이후에 제가 적을 글을 보면 쉽게 알 수 있으리라 생각합니다.


오늘은 PPL의 개념에 대한 이야기로 마치고 다음에는 PPL의 하나인 task에 대해서 이야기 하겠습니다.
시간 여유가 있거나 task에 대해서 빨리 알고 싶은 분들은 일전에 정재원님이 task 예제를 설명한 글을 올린 적이 있으니 먼저 그것을 보면서 예습을 하는 것도 좋습니다.



Concurrency Runtime

VC++ 10 Concurrency Runtime 2009. 7. 30. 06:00 Posted by 알 수 없는 사용자

VSTS 2010 VC++ 10의 큰 핵심 feature 두 가지를 뽑으라고 하면 저는 C++0x와 Concurrency Runtime 두 가지를 뽑고 싶습니다.

VC++ 10
은 시대의 변화에 맞추어 새로운 C++ 표준과 병렬 프로그래밍을 받아들였습니다.

현재도 Win32 API에 있는 Thread  관련 API를 사용하여 병렬 프로그래밍을 할수 있습니다. 하지만 이것만으로 병렬 프로그래밍을 하기에는 너무 불편합니다.
그래서 VC++ 10에는 Concurrency Runtime 이라는 것이 생겼습니다.



Concurrency
Parallel의 차이


Concurrency는 병행, Parallel은 병렬이라고 합니다.

Concurrency는 독립된 요구를 동시에 처리하고, Parallel은 하나의 task를 가능한 Concurrency로 실행할 수 있도록 분해하여 처리합니다.

< 그림 출처 : http://blogs.msdn.com/photos/hiroyuk/picture9341188.aspx >


VSTS 2010에서는 Concurrency는 런타임 용어 Paralell은 프로그래밍 모델 용어가 됩니다.
이를테면 프로그래밍 때에 분해하여 런타팀에 넘기면(이것이 병렬화), 런타임은 그것을 Parallel로 실행합니다. Concurrency Runtime은 Parallel 런타임으로 이해하면 될 것 같습니다.




Concurrency Runtime

< 그림 출처 : http://blogs.msdn.com/photos/hiroyuk/picture9341189.aspx >

Cuncurrency Runtime은 C++ 병행 프로그래밍 프레임워크입니다. Cuncurrency Runtime복잡한 parallel code 작성을 줄여주고, 간단하게 강력하고, 확장성 있고 응답성 좋은 parallel 애플리케이션을 만듭니다. 또한 공통 작업 스케줄러를 제공하며 이것은 work-stealing 알고리즘을 사용하여 프로세싱 리소스를 증가시켜 애플리케이션의 확장성을 높여줍니다.

 


Cuncurrency Runtime에 의해 다음의 이점을 얻을 수 있습니다.

1. data parallelism 향상 : Parallel algorithms은 컬럭션이나 데이터 모음을 복수의 프로세서를 사용하여 배분하여 처리합니다.

2. Task parallelism : Task objects는 프로세서 처리에 독립적으로 복수 개로 배분합니다.

3. Declarative data parallelism : Asynchronous agents와 메시지 전달로 어떻게 실행하지 몰라도 계산을 선언하면 실행됩니다.

4. Asynchrony : Asynchronous agents는 데이터에 어떤 일을 처리하는 동안 기다리게 합니다.

 

 

Cuncurrency Runtime 컴포넌트는 네 가지로 나누어집니다.

1. Parallel Patterns Library (PPL)

2. Asynchronous Agents Library (AAL)

3. work scheduler

4. resource manager

 

이 컴포넌트는 OS와 애플리케이션 사이에 위치합니다.


< 그림 출처 : MSDN >


Cuncurrency Runtime의 각 컴포넌트는 아래의 네 개의 헤더 파일과 관련 되어집니다.

컴포넌트

헤더 파일

Parallel Patterns Library (PPL)

ppl.h

Asynchronous Agents Library (AAL)

agents.h

Concurrency Runtime work scheduler

concrt.h

Concurrency Runtime resource manager

concrtrm.h

 

 

Concurrency Runtime을 사용하기 위해서는  namespace Concurrency를 선업합니다.

Concurrency RuntimeC Runtime Library (CRT)를 제공합니다.


Concurrency Runtime의 대부분의 type와 알고리즘은 C++의 템플릿으로 만들어졌습니다. 또한 이 프레임워크에는 C++0x의 새로운 기능이 많이 사용되었습니다.

대부분의 알고리즘은 파라메터 루틴을 가지고 작업을 실행합니다. 이 파라메터는 람다 함수, 함수 오브젝트, 함수 포인터입니다.



처음 들어보는 단어를 처음부터 막 나오기 시작해서 잘 이해가 안가는 분들이 있지 않을까 걱정이 되네요. 그래서 핵심만 한번 더 추려 보겠습니다.^^

1. Concurrency는 병행, Parallel은 병렬.
2. VSTS 2010에서는 Concurrency는 런타임 용어로 Paralell은 프로그래밍 모델 용어.
3. 프로그래밍 때에 분해하여 런타팀에 넘기면(이것이 병렬화), 런타임은 그것을 Parallel로 실행.
4. Cuncurrency Runtime은 C++ 병행 프로그래밍 프레임워크로 복잡한 parallel code 작성을 줄여주고, 간단하게 강력하고, 확장성 있고 응답성 좋은 parallel 애플리케이션을 만들수 있으며 공통 작업 스케줄러를 제공하며 이것은 work-stealing 알고리즘을 사용하여 프로세싱 리소스를 증가시켜 애플리케이션의 확장성을 높여준다.

5. Cuncurrency Runtime 컴포넌트는 네 가지로 나누어진다.

  1. Parallel Patterns Library (PPL)

  2. Asynchronous Agents Library (AAL)

  3. work scheduler

  4. resource manager



그럼 다음에는 Parallel Patterns Library(PPL)에 대해서 이야기 하겠습니다.^^





PPL task를 이용한 피보나치 수 계산

VC++ 10 Concurrency Runtime 2009. 7. 17. 00:43 Posted by 알 수 없는 사용자
오래만에 돌아왔습니다.

동영상 자막 번역은 영 아니다라는 판단하에(?), 일반적인 형식으로 C++0xVisual Studio 2010에서의 병렬 프로그래밍에 대해 글을 써볼 생각입니다.

일단 http://code.msdn.microsoft.com/concrtextras 에서 샘플 코드를 받으십시오. 그를 기준으로 당분간은 진행 예정입니다. 당연히 돌려보려면 Visual Studio 2010 베타1이 필요합니다.

오늘은 Parallel Patterns Library를 이용한 피보나치 수 계산 예부터 살펴보죠. 순차 수행 버전과 PPL의 태스크 기능을 이용한 병렬 수행 버전의 성능 비교가 첫번째 핵심 사항입니다. 그리고 malloc 대 Concurrency::Alloc의 성능 비교가 두번째 핵심 사항 되겠습니다.

소스를 찬찬히 살펴보죠.

    8 #include "windows.h"

    9 #include <ppl.h>

   10 

   11 using namespace Concurrency;

   12 

   13 int SPINCOUNT = 25;

   14 

   15 //Spins for a fixed number of loops

   16 #pragma optimize("", off)

   17 void delay()

   18 {

   19     for(int i=0;i < SPINCOUNT;++i);

   20 };

   21 #pragma optimize("", on)


먼저 헤더 파일 포함이 나오고, 각 작업의 계산 부하 조절을 위한 idle loop 함수가 나옵니다. pragma 디렉티브로 최적화를 해당 함수에 대해서만 끄고 있습니다. 그래야 실제 부하 조절 용도로 의미가 있겠죠.

   23 //Times execution of a functor in ms

   24 template <class Functor>

   25 __int64 time_call(Functor& fn)

   26 {

   27     __int64 begin, end;

   28     begin = GetTickCount();

   29     fn();

   30     end = GetTickCount();

   31     return end - begin;

   32 };


이 함수는 단순히 프로파일링(성능 측정)을 위한 유틸리티 함수 되겠습니다.

   34 //Computes the fibonacci number of 'n' serially

   35 int fib(int n)

   36 {

   37     delay();

   38     if (n< 2)

   39         return n;

   40     int n1, n2

   41     n1 = fib(n-1);

   42     n2 = fib(n-2);

   43     return n1 + n2;

   44 }


실제 가장 이해하기 쉬운 재귀 방식의 피보나치 수 구하기 함수입니다. 당연히 이 방식은 중복 계산이 많아 성능이 한참 떨어지게 되는데요, 이 글의 쟁점은 아니므로 넘어갑니다.

   45 //Computes the fibonacci number of 'n' in parallel

   46 int struct_fib(int n)

   47 {

   48     delay();

   49     if (n< 2)

   50         return n;

   51     int n1, n2;

   52 

   53     //declare a structured task group

   54     structured_task_group tasks;

   55 

   56     //invoke the first half as a task

   57     auto task1 = make_task([&n1,n]{n1 = struct_fib(n-1);});

   58     tasks.run(task1);

   59 

   60     //run the second recursive call inline

   61     n2 = struct_fib(n-2);

   62 

   63     //wait for completion

   64     tasks.wait();

   65 

   66     return n1 + n2;

   67 }


다음은 해당 알고리즘의 병렬 구현입니다. structured_task_group 변수를 하나 선언하고, make_task를 이용해 재귀 호출의 한쪽을 담당할 태스크를 만든 뒤, 태스크 그룹을 통해 돌립니다. tasks.wait() 호출을 통해 스폰한 태스크 작업이 마무리될 때까지 대기한 후, 최종 결과를 리턴하고 있습니다. C++0x의 람다가 쓰이고 있는 것을 확인하실 수 있습니다.
재귀적으로 호출이 되면서 꽤나 많은 태스크들이 만들어질텐데, 그냥 스레드 수준에서 직접 이런 작업을 했다면, 실제 하드웨어가 지원하는 병렬 수행의 수보다 과도하게 많은 스레드 생성이 이루어지면서, 상당히 비효율적으로 돌텐데요. PPL의 태스크 개념을 사용해 이렇게 보다 고수준에서 작업하면 그러한 오버헤드를 상당 부분 알아서 최적화 해줍니다.

   69 //Computes the fibonacci number of 'n' allocating storage for integers on heap

   70 int struct_fib_heap(int n)

   71 {

   72     delay();

   73     if (n< 2)

   74         return n;

   75     //n1 and n2 are now allocated on the heap

   76     int* n1;

   77     int* n2;

   78 

   79     //declare a task_group

   80     structured_task_group tg

   81 

   82     auto t1 = make_task([&]{

   83         n1 = (int*) malloc(sizeof(int));

   84         *n1 = struct_fib_heap(n-1);

   85     });

   86     tg.run(t1);

   87     n2 = (int*) malloc(sizeof(int));

   88     *n2 = struct_fib_heap(n-2);

   89     tg.wait();

   90     int result = *n1 + *n2;

   91     free(n1);

   92     free(n2);

   93     return result;

   94 }


malloc으로 힙에 버퍼를 잡아 결과를 리턴하는 버전입니다. Concurrency::Alloc과의 성능 비교를 위한 함수 되겠습니다.

   95 //Computes the fibonacci number of 'n' using the ConcRT suballocator

   96 int struct_fib_concrt_heap(int n)

   97 {

   98     delay();

   99     if (n< 2)

  100         return n;

  101     int* n1;

  102     int* n2;

  103     structured_task_group tg

  104     auto t1 = make_task([&]{

  105         n1 = (int*) Concurrency::Alloc(sizeof(int));

  106         *n1 = struct_fib_concrt_heap(n-1);

  107     });

  108     tg.run(t1);

  109     n2 = (int*) Concurrency::Alloc(sizeof(int));

  110     *n2 = struct_fib_concrt_heap(n-2);

  111     tg.wait();

  112     int result = *n1 + *n2;

  113     Concurrency::Free(n1);

  114     Concurrency::Free(n2);

  115     return result;

  116 }


이번에는 병렬 런타임의 suballocator를 이용하고 있습니다.

  117 int main()

  118 {

  119     int num = 30;

  120     SPINCOUNT = 500;

  121     double serial, parallel;

  122 

  123     //compare the timing of serial vs parallel fibonacci

  124     printf("computing fibonacci of %d serial vs parallel\n",num);

  125     printf("\tserial:   ");

  126     serial= (double)time_call([=](){fib(num);});

  127     printf("%4.0f ms\n",serial);

  128 

  129     printf("\tparallel: ");

  130     parallel = (double)time_call([=](){struct_fib(num);});

  131     printf("%4.0f ms\n",parallel);

  132 

  133     printf("\tspeedup: %4.2fX\n",serial/parallel);

  134 

  135     //compare the timing of malloc vs Concurrency::Alloc,

  136     //where we expect to get speedups because there are a large

  137     //number of small malloc and frees.

  138 

  139     //increase the number of tasks

  140     num = 34;

  141 

  142     //reduce the amount of 'work' in each task

  143     SPINCOUNT = 0;

  144 

  145     //execute fib using new & delete

  146     printf("computing fibonacci of %d using heap\n",num);

  147     printf("\tusing malloc:             ");

  148     serial= (double)time_call([=](){struct_fib_heap(num);});

  149     printf("%4.0f ms\n",serial);

  150 

  151     //execute fib using the concurrent suballocator

  152     printf("\tusing Concurrency::Alloc: ");

  153     parallel = (double)time_call([=](){struct_fib_concrt_heap(num);});

  154     printf("%4.0f ms\n",parallel);

  155 

  156     printf("\tspeedup: %4.2fX\n",serial/parallel);

  157 

  158     return 0;

  159 }


마지막으로  main 함수입니다. 단순히 각 함수를 호출하고 시간을 측정한 뒤 적절한 메시지를 출력하고 있습니다. 역시 람다가 쓰여 구문이 간결해졌습니다.

2 코어 머신에서 디버그 버전으로 돌려본 결과는 다음과 같습니다.


순차 대 병렬 버전의 경우 1.29배 속도 향상, malloc 대 Concurrency::Alloc의 경우 2.60배 속도 향상을 확인할 수 있습니다. 병렬 버전에서의 속도 향상이 생각보다 미미한데요. 제 컴퓨터가 좀 문제(?)가 있어서 그럴지도 모르겠습니다; 한편, 병렬 환경에서 메모리 할당 및 해제가 빈번한 경우, 반드시 병렬 런타임의 할당자를 써야겠군요.


추가: 제 블로그에 관련 주제로 글(두가지 C++ 차세대 병렬 플랫폼 간단 비교)을 하나 올렸습니다. 참고하세요. ^^

Welcome to F#(12) - 공동작업 좋치아니항가

F# 2009. 7. 16. 14:38 Posted by 알 수 없는 사용자

- F#의 첫 라이브러리 출연(Feat. C#)

오늘은 간단하게, F#에서 만든 코드를 C#에서 사용해보는 시간을 갖겠습니다. 자~ 간단하게 간단하게~.

우선, C#으로 콘솔프로그래밍 프로젝트를 하나 생성하고 같은 솔루션에 F# 라이브러리 프로젝트를 하나 추가합니다. 대략 아래와 같은 모양이 됩니다. 



그리고 Module1.fs에다가 아래의 코드를 작성합니다. 


 module FirstModule =
    type Type1 =
        { num: int }
        member self.reverse = { num = -self.num }
        member self.add(num2) = { num = self.num + num2 }
       
type Type2 =
    { num: int }
    member self.reverse = { num = -self.num }
    member self.add(num2) = { num = self.num + num2 }

let add5 num = num + 5



FirstModule라는 모듈을 선언하고 그 안에 Type1이라는 타입을 선언한 걸 보여주고 있습니다. 그리고 Type1의 멤버로는 int타입의 num이 있고 reverse, add라는 함수가 있습니다. 각각의 함수뒤의 {}안의 코드는 num에 새로운 값을 할당해서 Type1의 새로운 객체를 리턴하는 내용으로 보시면 이해가 되실 겁니다. 그리고 그 아래에는 모듈선언없이 그냥 Type2라는 타입을 선언했구요, 그 밑에는 함수 add5를 선언했습니다. 이런 코드들을 C#에서 참조한다면 어떻게 쓸 수 있을까요? 

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CoWorkWithFSharp
{
    class Program
    {
        static void Main(string[] args)
        {
            Module1.FirstModule.Type1 type1 = new Module1.FirstModule.Type1(5);
            Module1.FirstModule.Type1 type1_2 = type1.reverse;

            Console.WriteLine(type1_2.num);

            Module1.Type2 type2 = new Module1.Type2(6);
            Module1.Type2 type2_2 = type2.add(9);

            Console.WriteLine(type2_2.num);

            Console.WriteLine(Module1.add5(12));
        }
    }
}



위 코드를 보시면, Module1이라는 파일이름이 가장 먼저 오는 걸 볼 수 있습니다. 파일이름자체가 모듈이 되는거죠. 베타1이라서 그런지는 모르겠지만, F#으로 만든 코드는 "using Module1;" 형태로 using을 할 수 없었습니다. 에러메세지를 보면 네임스페이스가 아니라서 안된다고 하니 모듈도 클래스로 취급이 되는거 같은데 개선이 될지 아니면, 이대로 남을지 모르겠네요. 코드를 보시면 알겠지만, 매번 모듈명을 반복해서 적어줘야 하니 좀 빡세긴 합니다만;;;; 

아무튼, 저렇게 F#에서 작성한 타입의 객체를 생성할 수 있구요, 멤버 메서드와 함수를 호출할 수도 있습니다. 다만, 함수의 결과로 새로운 객체가 생성된다는 점 기억하시구요. 실행결과는 아래와 같습니다. 



그러면, 지난주에 만들었던 Discriminated Union을 이용한 간단한 사칙연산 코드를 C#에서 사용해보도록 하겠습니다. 아래코드를 Module1.fs에 추가하구요 

type Expr =
    | Num of float
    | Add of Expr * Expr
    | Sub of Expr * Expr
    | Mul of Expr * Expr
    | Div of Expr * Expr

let rec Calc expr =
    match expr with
    | Num num -> num
    | Add (e1, e2) -> (Calc e1) + (Calc e2)
    | Sub (e1, e2) -> (Calc e1) - (Calc e2)
    | Mul (e1, e2) -> (Calc e1) * (Calc e2)
    | Div (e1, e2) -> (Calc e1) / (Calc e2)



아래 코드를 C#코드에 추가해줍니다. 

 Module1.Expr expr = new Module1.Expr._Mul(
    Module1.Expr.Add(
        Module1.Expr.Num(5.0),
        Module1.Expr.Num(6.0)),
        Module1.Expr.Div(
            Module1.Expr.Num(5.0),
            Module1.Expr.Num(2.0)));

Console.WriteLine(Module1.Calc(expr));



위 코드를 보시면, 지난주에 계산을 위해서 만들었던 식을 그대로 C#에서 생성했습니다. 실행결과는 아래와 같습니다. 




-정리하며

아~ 간단한 포스트였군요~. 대략 F#에서 만든 코드를 이런식으로 C#등의 다른 언어에서 호출해서 사용할 수 있습니다. 어느덧 베타1이 나온지도 꽤 됀 느낌이네요. 많이들 익숙해지셨나요? VS2008에서 뭔가를 만들다가 C#4.0의 dynamic을 쓰면 딱이겠다고 생각하는 순간, 아쉬움이 느껴지더군요. 아직 F#은 그만큼 익숙하지 못해서 그런경우는 없었던거 같네요-_-;;; 언제쯤 F#의 Jedi가 될 수 있을까요? ㅋㅋㅋ.


-참고자료

1. Expert F#, Don Syme, Adam Granicz,Antonio Cisternino, Apress

Welcome to F#(11) - 차별을 권장하는 언어인거임?!?!

F# 2009. 7. 8. 22:51 Posted by 알 수 없는 사용자

- 이말이 사실인게냐

discriminate는 (1)여러가지가 있을때 그것들이 서로 다르다는 걸 인식할 수 있다는 의미 (2) 특정한 단체의 사람들에게 against해서 쓰일때는 그 사람들에게 뭔가 부당하게 불이익이나 이익을 주는 의미를 가지고 있습니다.

즉, 둘다 차이에 근거한 의미인데요 첫째는 구별정도가 되겠고 둘째는 차별정도가 되겠네요. F#은 Discriminated Union(이하 union)이라는 아주 쓸모있는 기능을 가지고 있습니다. 그렇다면 F#은 차별을 권장하는 언어로서 인류발전에 그닥 긍정적이지 못한 언어일까요? 다행히 F#에서의 Discriminated는 첫번째인 구별의 의미로 쓰입니다.(뭐가 다행이지?-_-)


- 영단어엔 관심없는거임. 소스내놓으라해.

union의 형태는 아래와 같습니다. 


  type type-name =
   | case-identifier1 [of type1 [ * type2 ...]
   | case-identifier2 [of type3 [ * type4 ...] 


type키워드에 타입이름이 따라오고요 그리고 등호기호, 그리고 수직파이프(|) 문자로 나눠지는 식별자와 식별자의 타입리스트가 이어집니다. 저기서 각각의 식별자(case-identifier)를 discriminator(이하 구분자)라고 하는군요. 바로 차별구분자인거죠. 사칙연산 계산기를 구현하는 짧막한 코드를 완성해가면서 알아볼까요?


 type Expr =
    | Num of float
    | Add of Expr * Expr
    | Sub of Expr * Expr
    | Mul of Expr * Expr
    | Div of Expr * Expr


위 코드는 간단한 사칙연산에 필요한 구분자를 union을 이용해서 선언한 코드입니다. 다섯개의 구분자를 정의하고 있는데요. Num은 그냥 float타입이고, 나머지는 float * float인 tuple타입입니다. 나머지코드를 보시기전에 이 union만 가지고 이야기 해보도록 하죠. 이걸 어케 쓸까요? 


 

위의 그림을 보시면, 저렇게 쓰는거구나~ 하고 아실 수 있습니다. 구분자의 이름을 주고 괄호안에 구분자의 타입에 해당하는 값을 넣어주면 되는거죠. 그러면 Expr타입의 Num이라는 구분자이고 값은 float타입인 5.0을 가지고 있구나~ 하고 알마먹는 것이죠. Add도 마찬가지로 이해할 수 있습니다. 그리고 위의 union의 정의에 이어서 계산에 쓸 수식을 하나 정의해보죠. 

 let expr = Mul(Add(Num(5.0), Num(6.0)), Div(Num(5.0), Num(2.0))) // (5+6) * (5 /2)


주석에 나와있듯이 (5+6) * (5/2)를 위의 구분자로 표현한 수식입니다. 그러면, union을 이렇게 선언해서 구분자를 저렇에 선언한다는건 알겠는데, 저걸 어따써먹는건지 하는 궁금증이 생깁니다. union과 찰떡궁합인게 바로 pattern matching입니다. 아래의 코드를 보시죠

 let rec Calc expr =
    match expr with
    | Num num -> num
    | Add (e1, e2) -> (Calc e1) + (Calc e2)
    | Sub (e1, e2) -> (Calc e1) - (Calc e2)
    | Mul (e1, e2) -> (Calc e1) * (Calc e2)
    | Div (e1, e2) -> (Calc e1) / (Calc e2)

 
위의 코드는 제귀호출이 가능한 Calc라는 메서드인데요 expr이라는 인자를 하나 받습니다. 그리고 그 expr을 가지고 일치하는 패턴을 찾습니다. 말로 하지 말고 그림으로 하겠습니다! 아래의 그림을 보시져.(잘 안보이심 클릭해서 크게보시길...-_-)
 

 

왼쪽 상단은 F# Interactive에서 입력한 부분이고, 오른쪽 하단은 패턴매칭하는 코드 부분이죠. Num(5.0)의 실제 타입은 "Num 5.0"이고 그게 그대로 "Num num"부분에 매칭이 되면서, num의 값은 5.0이 되는거죠. 패턴매칭의 결과로 (Num 5.0) 은 그냥 5.0을 리턴합니다. 그리고 Add(Num 4.0,Num 9.0)도 각각의 Num이 e1, e2에 매칭이 돼서 Calc를 다시 호출하는 모습입니다. 물론 결과적으로 (Num 4.0) -> 4.0, (Num 9.0) -> 9.0이 리턴되면서 두수의 합이 리턴되겠죠. 그러면 마지막으로 결과를 출력합니다.
 

 Calc expr |> printfn "%f"


위의 코드를 실행한 결과는 아래와 같습니다.
 


제대로 결과가 출력된 게 보이시죠? ㅋㅋㅋ. 그럼 오늘도 지난번 처럼 ildasm을 통해서 IL코드를 둘러볼까 합니다.

 
위 사진을 보시면 지난 포스트의 curry처럼 Expr도 클래스로 선언된걸 확인하실 수 있고, union의 구분자중의 하나인 Num도 Expr내부에 중첩된 클래스로 선언되어 있는걸 보실 수 있습니다. 그리고 Num이 float을 하나 가지는데 그게 num1이라는 이름으로 선언되어있는 걸 IL코드를 통해서도 확인해보실 수 있습니다. 즉, 내부적으로는 구분자별로 클래스를 선언해서 타입으로 검사를 하는걸로 생각할 수 있겠네요. 아래에 보시면 그 타입검사를 위한 메서드들이 선언되어있는걸 확인하실 수 있습니다.
 

 

그리고 마지막으로 MSDN에 나와있는 예제를 하나 보도록 하겠습니다. 

type MilkOption =
   | Nonfat
   | TwoPercent
   | Whole
   | Soy
   | Rice
  
type FlavorOption = string
  
type Brand = string

type Size =
   | Short
   | Tall
  
type TeaFlavor = string
type Shots = int

type CoffeeType =
   | Drip
   | EspressoShot
   | Cappuccino
   | Latte of Size * MilkOption * Shots
   | FlavoredLatte of Size * MilkOption * FlavorOption * Shots

type Drink =
   | Can of Brand
   | Coffee of CoffeeType
   | Tea of TeaFlavor

let drink1 = Can("Coke")
let drink2 = Coffee(Drip)
let drink3 = Coffee(Latte(Tall, Nonfat, 2))
// A single short soy latte with hazelnut
let drink4 = Coffee(FlavoredLatte(Short, Soy, "Hazelnut", 1))

union을 통해서 커피가 캔인지 뽑은 커피인지, 커피이외의 차종류인지부터 시작해서 커피의 종류에서, 사이즈, 샷의횟수등 까지 절묘하게 조합하는 모습을 보실 수 있습니다.

 

- 정리하며

F#은 이렇게 union을 쉽게쓸 수 있는 언어적 특성덕분에 DSL(Domain Specific Language, 다른말로 Language Oriented Progamming이라고도 함)을 잘 지원할 수 있는 능력이 있습니다. 물론 DSL은 그리 쉬운 주제는 아니지만, F#과 함께 천천히 시작해보는 것도 나쁘진 않을 거 같습니다. DSL이나 LOP에 대해서 더 설명을 드릴 수 있으면 좋겠지만, 내공이 허락치 않는군요-_-;;;;;;;;;;


- 참고자료

1.  http://msdn.microsoft.com/en-us/library/dd233226(VS.100).aspx
2.  http://sdasrath.blogspot.com/2009/02/20090220-f-types-discriminated-unions.html
3. Expert F#,  Don Syme, Adam Granicz, Antonio Cisternino, Apress

일곱,여덟번째로 드디어 마지막입니다. ㅠㅠ

역시나 Q&A가 이어지고, 끝에 선물을 주네요.


그 동안 시청해주셔서 감사합니다. 조만간 새로운 내용으로 다시 돌아오겠습니다. ^^

p.s. 필요하다는 분이 계서서 이제까지의 자막 파일들 첨부합니다.