소프트웨어/C# & ASP.NET

.NET Enterprise Services 성능

falconer 2008. 9. 5. 11:04

원문 : http://www.microsoft.com/korea/msdn/library/ko-kr/dev/dotnet/entsvcperf.aspx

.NET Enterprise Services 성능

Richard Turner, 프로그램 관리자, XML Enterprise Services
Larry Buerk, 프로그램 관리자, XML Enterprise Services
Dave Driver, 소프트웨어 디자인 엔지니어, XML Enterprise Services

Microsoft Corporation

2004년 3월

적용 대상:
   COM+ 구성 요소
   Microsoft .NET Enterprise Services

요약: 다른 활성화 및 호출 패턴에 적용될 때의 원시 COM+ 및 .NET Enterprise Services 구성 요소 성능을 확인합니다. .NET Enterprise Services 구성 요소를 C++의 COM+ 구성 요소처럼 빨리 실행하기 위한 지침과 함께 고성능 .NET Enterprise Service 구성 요소를 만드는 데 도움이 되는 주요 권장 사항을 살펴봅니다(45페이지/인쇄 페이지 기준).

관련 EnterpriseServicesPerf.exe 코드 샘플을 다운로드하십시오.

목차

소개
관리되는 코드로 마이그레이션해야 하는 이유
코드 변경 정도 지정
.NET Enterprise Services에 COM+ 연결
.NET Enterprise Services와 COM+ 성능 비교
테스트 결과 및 분석
결론
부록 1: 성능 권장 사항
부록 2: "Indigo" 및 .NET의 동향
부록 3: 분산 트랜잭션이 성능에 미치는 영향
부록 4: 참고 자료
부록 5: 성능 테스트 소스 코드
부록 6: 테스트 결과

소개

COM+ 코드를 "원시" Visual C++ 또는 Visual Basic 6에서 관리되는 .NET Enterprise Services 구성 요소로 이동할 것을 고려 중인 개발자는 다음과 같은 고민 사항이 있을 수 있습니다.

  • 왜 관리되는 코드로 전환해야 하는가?
  • 코드를 얼마나 변경해야 하는가?
  • Enterprise Services 구성 요소는 어떻게 수행되는가?
  • COM+ 및 .NET Enterprise Services가 제시하는 미래는 어떠한가?

이 문서에서는 특히 성능에 대한 질문을 위주로 위 주제를 다룰 것입니다. 이러한 주제에 대한 자세한 내용은 부록 4: 참고 자료에 있는 리소스를 참조하십시오.

이 문서는 COM+ 구성 요소를 개발하고 코드를 .NET Enterprise Services로 마이그레이션할 것을 고려 중인 개발자와 설계자를 대상으로 합니다.

관리되는 코드로 마이그레이션해야 하는 이유

개발자가 .NET에서 코드를 개발해야 하는 이유는 여러 가지입니다. 다음은 코드 개발의 몇 가지 이점입니다.

  • 향상된 개발자 생산성: 개발자들은 종종 .NET을 사용하여 개발할 때 작성해야 하는 "통로 코드"가 훨씬 적기 때문에 응용 프로그램 논리를 작성하는 데 더 많은 신경을 쓸 수 있습니다. 또한 대부분의 개발자는 .NET에서 제공하는 명확하고 일관되게 구성된 풍부한 리소스 라이브러리 덕분에 다른 기술과 비교하여 훨씬 빨리 배울 수 있습니다.
  • 향상된 코드 안정성 및 보안: 개발자는 원시 코드보다는 .NET을 사용할 때 안정적이고 보안된 코드를 더욱 쉽게 작성할 수 있습니다. 그 이유는 코드 액세스 보안 및 CLR(공용 언어 런타임) 같은 기능 때문입니다. 이러한 기능은 NET 코드가 다른 실행 중인 코드에 의도하지 않은 영향을 미치는 것을 방지하고 해커가 .NET 코드를 사용하여 환경을 방해하거나 제어할 수 있는 기회를 줄이는 데 도움이 됩니다.
  • 향상된 성능 및 확장성: 개발자는 .NET으로 마이그레이션할 때 코드의 성능과 확장성이 향상되는 것을 알 수 있습니다. 모든 .NET 언어가 다중 스레딩 같은 기능을 지원하고 활용하기 때문입니다.
  • XCOPY 배포: 대부분의 .NET 응용 프로그램에서는 필요한 파일을 하드 드라이브의 폴더로 복사하고 공유 구성 요소를 운영 체제에 선택적으로 등록하기만 하면 배포가 이루어집니다. 이는 다른 응용 프로그램에서 일반적으로 사용하는 것보다 훨씬 명쾌한 배포 전략입니다.

개발자가 .NET으로 마이그레이션하기를 원하는 10가지 이유에 대해서는 Top 10 Reasons for Developers to Use the .NET Framework 1.1 을 참조하십시오.

.NET은 시스템 관리자에게도 이점을 제공합니다. 관리자가 .NET으로 이동하기를 원하는 10가지 이유가 기록된 목록을 보려면 Top 10 Reasons for Systems Administrators to Use the .NET Framework 1.1 을 참조하십시오.

일반 C++ 코드를 관리되는 C++로 마이그레이션하는 방법에 대한 소개는 Managed Extensions for C++ Migration Guide의 Introduction to Wrapping C++ Classes 를 참조하십시오.

코드 변경 정도 지정

대부분의 경우 COM+ 코드를 .NET Enterprise Services에 연결하려면 COM+ 구성 요소가 어떠한 언어로 개발되었는지를 비롯한 다수의 요인에 따라 몇 가지 수동 작업이 필요합니다. 예를 들어, Visual Basic 6 개발자는 앞으로 나올 Visual Studio 2005의 도구에서 서브루틴 및 함수의 본문을 수정하지 않고도 클래스 정의 및 메서드 서명을 변환할 수 있는 몇 가지 지원을 받게 됩니다(다른 변환된 메서드에 대한 호출은 제외).

C++ 개발자는 COM+ 코드에서 .NET 코드로 변환하는 작업을 대부분 수동으로 수행해야 하지만, 대부분의 통로가 ATL(Active Template Library) 같은 클래스 라이브러리 대신 CLR에 구현되기 때문에 변환된 코드가 좀 더 간결해지는 것을 확인할 수 있습니다. C++ 개발자는 응용 프로그램을 연결할 언어를 선택할 수도 있습니다. 예를 들어, 기존 코드 기반을 최대한 활용하기 위해 관리되는 C++를 선택하거나, 더욱 간결한 코드를 위해 C#을 선택할 수 있습니다.

.NET Enterprise Services에 COM+ 구성 요소 연결

기존 COM+ 코드를 Visual Basic 6 및 Visual C++ 같은 원시 프로그래밍 언어와 도구로부터 마이그레이션하는 개발자는 .NET 코드로 완벽하게 변환하기 위해 몇 가지 기존 코드를 수정해야 합니다. 여기에 동반되는 작업 양은 사용할 수 있는 기존 코드 기반 및 도구에 따라 다릅니다. 다음 표에는 코드를 .NET으로 마이그레이션할 때 고려할 옵션이 요약되어 있습니다.

변환하기 전의 언어 변환한 후의 언어 코드 변환 도구 사용 가능 여부 필요한 코드 변환 작업
Visual Basic 6 Visual Basic .NET 예(Visual Studio 2005) 대부분의 Visual Basic 6 코드는 Visual Basic .NET에 직접 연결됩니다. Visual Studio 2005의 Visual Basic 6 코드 마이그레이션 도구는 대부분의 클래스와 메서드 선언 및 형식을 Visual Basic .NET 구문으로 변환합니다.
Visual C++ Visual C++ .NET 아니요 C++ .NET은 특히 원시 코드와 .NET 사이에서 상호 작동하는 코드를 작성하는 데 유용합니다.
Visual C++ Visual C# 아니요 C# 구문은 많은 면에서 C++와 비슷합니다. 변환을 수행하려면 개발자의 특정 작업이 필요합니다.

.NET 특성

COM+ 구성 요소를 설치한 후에는 구성 요소 서비스 스냅인 도구를 사용하여 수동으로 구성하거나 스크립트 또는 코드를 통해 구성해야 합니다.

예를 들어, 구성 요소에 트랜잭션 지원이 필요하다고 표시하고 각 구성 요소의 AddSale() 메서드가 오류 없이 완료될 경우 트랜잭션을 자동으로 커밋하도록 지정하는 상황을 가정합니다. 그러기 위해서는 구성 요소 및 필요한 메서드의 속성을 COM+ 구성 요소 서비스 관리 콘솔에서 수동으로 구성해야 합니다.

  1. 구성 요소 서비스 관리 콘솔을 열고 올바른 응용 프로그램 및 구성 요소를 탐색합니다.

  2. 이 구성 요소의 속성을 열고 트랜잭션 탭을 클릭한 다음 필수를 클릭합니다.

  3. 확인을 클릭하고 구성 요소 탐색기를 통해 ATLPerfTests 개체의 AddSale() 메서드를 찾습니다.

  4. AddSale() 메서드를 마우스 오른쪽 단추로 클릭하고 메서드의 속성을 연 다음 이 메서드가 반환하면 자동으로 이 개체 비활성화 확인란을 선택합니다.

관리자는 일반적으로 보안 및 ID 설정 같은 배포 지향 설정이나 역할 구성원 및 재활용 같은 런타임 지향 설정을 구성합니다. 개발자는 트랜잭션 지원과 같은 구성 요소의 개발 지향 기능을 구성합니다. 그러나 COM+에서는 개발자가 코드 내에서 구성 요소의 구성 방법을 지정하기가 어렵습니다. Visual Basic 개발자는 구성 요소에 필요한 트랜잭션 지원을 어느 정도 지정할 수 있지만 C++ 개발자는 그렇지 못합니다. COM+ 구성 요소를 안정적이면서 반복적으로 설치하기 위해서는 스크립트, 설치 관리자 응용 프로그램 또는 설치 지침을 작성해야 합니다.

.NET은 개발자가 구성 요소의 코드 내에서 구성 요소에 필요한 서비스와 해당 서비스의 구성 방법을 지정할 수 있도록 함으로써 구성 요소 구성을 단순화합니다. 구성 요소가 설치되면 플랫폼에서 구성 설정을 자동으로 구성하지만, 이러한 구성 설정은 설치 후에 변경할 수 있습니다.

COM+에서는 설치 후에 구성 설정을 변경할 수 있기 때문에 이러한 속성을 다수 변경할 때는 매우 주의를 기울여야 합니다. 예를 들어, 보안 설정을 변경하면 개체를 인스턴스화하고 개체의 메서드를 호출할 수 있는지의 권한 지정에 영향을 미칠 수 있습니다. 반면에 트랜잭션 지원을 제거하면 구성 요소가 안정적이지 못하고 예측 불가능해질 수 있으며 구성 요소의 데이터가 손실되거나 손상될 수도 있습니다.

개발자는 특성(C# 및 C++에서는 [attribute]와 같이 대괄호 안에 표시되고, Visual Basic .NET에서는 <attribute>와 같이 꺾쇠괄호 안에 표시됨)을 사용하여 어셈블리, 응용 프로그램, 구성 요소 또는 메서드의 요소를 구성합니다. 예를 들어, 아래 코드에서는 SimpleTest 구성 요소에 트랜잭션이 필요하며, AddSale() 메서드가 오류 없이 완료될 경우 트랜잭션이 자동으로 완료된다는 것을 보여줍니다.

C#

   [Transaction(TransactionOption.RequiresNew)]
public class SimpleTest: ServicedComponent
{
...
[AutoComplete]
public void AddSale(int orderNumber, int storeID, int
titleID, int qty)
{
...
}
...
}

Visual Basic .NET

<Transaction(TransactionOption.RequiresNew)> _
Public Class VBTestObject : Inherits ServicedComponent
...
<AutoComplete> _
Public Function Sum(ByVal number1 As Integer, ByVal number2
As Integer) As Integer
...
End Function
...
End Class

다음 표에는 Enterprise Services 구성 요소에 적용할 수 있는 주로 사용되는 특성이 들어 있고, 설치 후에 특성을 안전하게 변경할 수 있는지 여부가 표시되어 있습니다.

특성 범위 기본 소유자 실행 중에 안전하게 수정 가능한지의 여부
ApplicationAccessControl  어셈블리 개발자 아니요.
개발자가 응용 프로그램의 보안 방법에 대한 하드 종속성 또는 묵시적 종속성을 사용하여 코드를 작성했을 수 있습니다.

액세스 제어를 낮추면 시스템 보안이 손상될 수 있습니다.

ApplicationActivation  어셈블리 관리자 예. 그러나 주의해야 합니다.
라이브러리 또는 서비스로 변경하거나 이로부터 변경하면 성능에 영향을 미칠 수 있으며 대기 중인 구성 요소가 손상될 수 있습니다.
ApplicationID  어셈블리 개발자 예. 그러나 주의해야 합니다.
이 특성을 변경하면 하드 코딩된 구성 요소 등록 도구에 영향을 미칠 수 있습니다.
ApplicationName  어셈블리 개발자 예. 그러나 주의해야 합니다.
이 특성을 변경하면 하드 코딩된 구성 요소 등록 도구에 영향을 미칠 수 있습니다.
ApplicationQueuing  어셈블리 개발자 아니요.
AutoComplete  메서드 개발자 아니요.
아래의 "Transaction"을 참조하십시오.
ComponentAccessControl  클래스 개발자 아니요.
개발자는 구성 요소의 보안 방법에 대한 하드 종속성 또는 묵시적 종속성을 사용하여 코드를 작성했을 수 있습니다.

액세스 제어를 낮추면 시스템 보안이 손상될 수 있습니다.

ConstructionEnabled  클래스 관리자 예.
생성자 문자열을 변경할 수는 있지만, 설정/해제 상태를 전환하지는 마십시오.
Description  어셈블리
클래스
메서드
인터페이스
관리자 예.
EventTrackingEnabled  클래스 관리자 예.
InterfaceQueuing  클래스
인터페이스
개발자 아니요.
JustInTimeActivation  클래스 개발자 아니요.
MustRunInClientContext  클래스 개발자 아니요.
구성 요소가 클라이언트의 컨텍스트와 호환되지 않을 수도 있습니다.
ObjectPooling  클래스 개발자 예. 풀 설정은 변경할 수 있습니다.
구성 요소가 손상될 수 있기 때문에 개체 풀링 설정/해제 상태를 전환하지는 마십시오.
PrivateComponent  클래스 개발자 아니요.
이 구성 요소를 공개적으로 호출할 수 없도록 개발자가 특별히 지정한 것입니다. 구성 요소가 알 수 없는 사용 패턴을 지원하는지에 대한 테스트를 받지 않았을 수도 있습니다. 공개되는 경우 심각한 보안 위협을 초래할 수 있습니다.
SecurityRole - 역할 이름  어셈블리
클래스
인터페이스
개발자 아니요. 역할을 제거하지 마십시오.
개발자가 지정된 역할에 대한 명시적 종속성 또는 역할 존재에 대한 묵시적 예외를 사용하여 코드를 작성했을 수 있습니다.
SecurityRole - 역할 구성원  어셈블리
클래스
인터페이스
관리자 예. 그러나 주의해야 합니다.
액세스 제어를 너무 광범위하게 공개하면 시스템의 보안이 손상되고, 너무 제한되게 공개하면 액세스가 과도하게 제한될 수 있습니다.
Synchronization  클래스 개발자 아니요.
Transaction  클래스 개발자 아니요.
구성 요소의 트랜잭션 지원을 수정하면 시스템의 안정성과 무결성이 손상될 수 있습니다.

위 표는 구성 요소 개발자가 기본적으로 소유하는 구성 요소와 관리자가 기본적으로 소유하는 구성 요소도 보여 줍니다. 코드에 미치는 영향을 자세히 알고 있는 경우가 아니라면 개발자의 설정을 변경하지 않는 것이 좋습니다. 개발자가 지정한 보안 관련 특성을 관리자가 변경할 수는 있지만, 이 경우에는 구성 요소 또는 응용 프로그램에 대한 액세스를 충분히 제한하되 너무 과도하게 제한하지는 않도록 주의를 기울여 보안을 구성해야 합니다.

특성을 통해 개발자는 자신의 구성 요소에 대한 구성 요구 사항을 간단하고 효율적으로 지정하면서 설치 후에 관리자가 구성 설정을 변경할 수 있도록 합니다. Enterprise Services가 제공하는 특성에 대한 자세한 내용은 .NET Framework Class Library 를 참조하십시오.

.NET Enterprise Services와 COM+ 성능 비교

Enterprise Services의 성능을 COM+와 비교하여 측정하기 위해 다음 언어로 구성 요소를 만들었습니다.

  • Visual C++ .NET 및 ATL COM+
  • Visual Basic 6 COM+
  • C# 및 .NET Framework 1.1 Enterprise Services
  • Visual Basic .NET 및 .NET Framework 1.1 Enterprise Services

각 구성 요소에는 두 개의 공용 메서드가 포함되어 있습니다.

  • Sum(): 이 간단한 메서드는 두 숫자를 합해 디스크 또는 데이터베이스 액세스 작업을 수행하지 않는 간단한 작업을 시뮬레이트합니다.
  • AddSale(): 이 일반적인 메서드는 트랜잭션되며, 테이블에 레코드를 삽입하고 반환 전에 트랜잭션을 완료하는 개인 메서드인 InsertSale()을 호출합니다. 이 메서드는 일반적인 비즈니스 응용 프로그램 작업을 수행하는 "실제" 메서드의 성능 특징을 보여 줍니다.

그 다음에는 각 구성 요소에 대해 다음 테스트를 수행한 테스트 프로그램을 만들었습니다.

  • 반복적인 만들기/호출/릴리스: 이 테스트는 개체를 만들고 호출하고 릴리스하는 과정을 반복적으로 수행합니다.
  • 만들기/반복 호출/릴리스: 이 테스트는 개체를 인스턴스화하여 수천 번 호출한 다음 마지막에 개체를 릴리스합니다.

테스트 프로그램에서는 각 구성 요소에 대해 두 가지 테스트를 모두 실행했으며 결과를 쉼표로 분리된 파일에 작성하는 고해상도 타이머를 사용하여 각 테스트를 수행하는 데 걸린 시간을 측정했습니다. 이 파일의 결과를 Microsoft Excel로 가져와서 분석했습니다. 각 구성 요소에 대한 코드 목록은 부록 5: 성능 테스트 소스 코드를 참조하십시오.

테스트는 다음 표에 표시된 구성으로 설정된 컴퓨터에서 실행되었습니다.

  컴퓨터 1: 서버 컴퓨터 컴퓨터 2: 클라이언트 컴퓨터/단일 컴퓨터
CPU Dual Pentium 4 Xeon 3.06GHz Dual Pentium 4 Xeon 2.8GHz
RAM 1GB 1GB
디스크 로컬 SCSI 로컬 SCSI
네트워크 기가비트 이더넷 기가비트 이더넷
OS 및 .NET Windows Server™ 2003
.NET Framework 1.1
Windows Server 2003
.NET Framework 1.1

다른 하드웨어에서 테스트 응용 프로그램을 실행할 때 나타나는 특정 결과는 아래에 보고된 결과는 물론, 서로 간에도 다를 수 있습니다. 그러나 각 결과는 여기에 소개된 결과와 비례해야 합니다.

테스트 결과 및 분석

다음 절에서는 이전에 논의한 코드에 대해 실행한 성능 테스트의 결과를 분석합니다. 이 결과는 위에 나열된 하드웨어와 소프트웨어에서 테스트를 실행하여 얻은 것입니다. 모든 결과의 목록은 부록 6: 테스트 결과에 포함되어 있습니다.

결과를 보여 주는 다음 차트에서 차트 막대 길이가 길거나 숫자가 클수록 성능이 뛰어난 것입니다.

개체 활성화 및 삭제 성능

먼저 C++ 및 Visual Basic 6을 사용하여 개발한 원시 COM+ 구성 요소의 성능을 통해 COM+ 인프라가 어떻게 수행되는지를 살펴보겠습니다. 아래 차트는 개체 만들기, 간단한 메서드 호출 및 개체 릴리스를 반복적으로 수행함으로써 얻은 초당 호출 수를 보여 줍니다.

MTS(Microsoft Transaction Server) 1.0의 원래 디자이너는 이와 비슷한 데이터를 보고서 호출을 배달하는 데 필요한 인프라, 즉 프록시, DCOM(또는 프로세스간) 채널, 스텁 및 컨텍스트를 설정함으로써 프로세스 간 및 컴퓨터 간 활성화 시간에 큰 영향을 미칠 수 있다는 것을 깨달았습니다. 이 점이 다음을 수행하는 JIT(Just In Time) 활성화를 디자인하게 된 기본적인 동기였습니다.

  • 서버 구성 요소가 반환 이전에 SetComplete() 또는 SetAbort()를 호출하여 자체 수명 주기를 제어할 수 있도록 합니다.
  • 여러 메서드 호출을 사용하여 DCOM 통로를 설정하는 부담을 줄입니다.

다음 차트는 JIT 활성화를 활용하는 수정된 테스트를 단일 개체를 만들고 두 숫자를 합하는 간단한 메서드를 반복적으로 호출한 다음 마지막에 개체를 릴리스하는 방법으로 실행할 경우 발생하는 상황을 보여 줍니다.

이 결과는 JIT 활성화를 사용할 때 초당 호출 수 면에서 성능이 크게 향상되는 것을 보여 줍니다. JIT 활성화 및 Visual Basic 6을 사용하면 JIT 활성화를 사용하지 않는 C++보다 거의 33배 빠른 결과를 얻을 수 있습니다. JIT가 활성화된 Visual Basic 6에서는 초당 호출 수가 약 8600인 반면 JIT가 활성화되지 않은 Visual C++에서는 초당 호출 수가 약 261입니다.

작업을 수행하는 데 필요한 통로를 설정한 이후 컴퓨터 간 호출을 수행하면 네트워크가 성능에 큰 영향을 미치게 됩니다. 이 경우 Visual Basic 6 및 C++는 성능이 거의 비슷하지만, 그래도 Visual Basic 6가 C++보다 88% 빨리 수행됩니다.

Enterprise Services에서는 필요한 통로를 설정하는 추가 작업이 수행됩니다. 특히 개체를 생성하고 릴리스하기 위한 추가 호출이 필요합니다. 따라서 개체에서 아무런 작업을 수행하지 않고 단순히 개체를 만들었다 제거하는 경우를 비교할 때 이러한 추가 왕복 부담이 성능 비교에 큰 영향을 미치게 됩니다. Visual Studio 2005에서는 Enterprise Services가 향상되어 이러한 활성화 왕복 중 하나가 제거되었기 때문에 "활성화/단일 호출/릴리스" 패턴을 사용할 때 .NET Framework 1.1과 비교하여 성능이 20-30% 향상됩니다. 그러나 가능하면 이러한 패턴은 사용하지 않는 것이 좋습니다.

JIT 활성화를 사용할 때 C++ 및 Visual Basic 6의 성능이 비슷하다는 것을 고려하면 C# 및 Visual Basic .NET을 사용하는 Enterprise Services도 대략 같은 성능을 보일 것으로 예상할 수 있습니다. 아래 그림은 간단한 메서드를 호출하는 위 테스트를 실행한 결과를 보여 줍니다.

이 데이터는 단순히 두 정수를 합하고, 개체의 컨텍스트에 SetComplete()를 호출하고, 결과를 반환하는 간단한 메서드에 대해 수행한 초당 호출 수를 보여 줍니다. 개체를 활성화하고 릴리스하는 부담은 거의 사라졌지만 버퍼를 마샬링하고 호출 스택으로 변환하는 등의 작업으로 인해 호출을 수행하는 부담은 아직 남아 있습니다.

이처럼 매우 간단한 메서드에서도 프로세스 간 호출의 경우 Enterprise Services는 Visual Basic 6과 성능이 거의 비슷합니다. 컴퓨터간에 호출할 경우에는 모든 언어의 성능이 거의 비슷합니다.

그러나 일반적인 비즈니스 응용 프로그램은 메서드에서 이보다 더 복잡한 작업을 수행합니다. 다음 차트는 일반적인 메서드를 호출하여 분산 트랜잭션 내에서 데이터베이스 연결을 열고 간단한 SQL 문을 실행하는 동일한 응용 프로그램을 네 가지 언어로 작성했을 때 각각의 상대적인 성능을 보여 줍니다.

앞의 결과는 메서드 내에서 많은 작업을 수행할 경우 모든 언어의 결과가 실험 오차 범위 내에서 동등하다는 것을 보여 줍니다. ADO를 사용하는 C++ 및 Visual Basic 6을 통해 작성한 COM+ 원시 응용 프로그램은 Enterprise Services를 사용하는 C# 또는 Visual Basic .NET 응용 프로그램과 동일한 속도로 수행됩니다. 프로세스 간 작업을 실행하거나 컴퓨터 간 작업을 실행하는 경우 성능 면에서는 거의 차이가 없습니다.

결과 요약

위 결과는 구성 요소를 최대한 효율적으로 수행하는 데 있어서 JIT 활성화 및 "만들기/반복 호출/릴리스" 패턴이 매우 중요하다는 것을 보여 줍니다.

결론

코드를 .NET으로 마이그레이션하는 것이 유리한 몇 가지 주요 이유를 설명했습니다. 다른 활성화 및 호출 패턴에 적용될 때의 원시 COM+ 및 .NET Enterprise Services 구성 요소 성능에 대해서도 논의했습니다. 또한 지침을 따라가면서 .NET Enterprise Services 구성 요소가 C++ COM+ 구성 요소만큼 빨리 실행된다는 것을 보여 주었습니다. 부록 1: 성능 권장 사항에는 고성능 .NET Enterprise Service 구성 요소를 만드는 데 도움이 되는 주요 권장 사항이 나와 있습니다.

여기에 설명된 기술을 일관되게 적용하면 기존 COM+ 코드를 .NET Enterprise Service 구성 요소로 바로 변환하고 아무런 성능 저하 없이 .NET Framework의 사용성, 보안 및 개발자 생산성 등의 이점을 누릴 수 있습니다.

지금 COM+ 구성 요소를 Enterprise Services 구성 요소로 변환해 놓으면 나중에 코드를 "Indigo"로 더욱 쉽게 마이그레이션할 수 있다는 점도 중요합니다. 부록 2: "Indigo" 및 .NET의 동향에서는 이 주제에 대해 간략히 논의합니다.

부록 1: 성능 권장 사항

다음 절에서는 높은 수준의 성능을 제공하는 빠른 COM+ 및 Enterprise Services 구성 요소를 만드는 방법에 대한 팁과 안내를 제공합니다. 대부분의 제안은 .NET Enterprise Services 구성 요소와 원시 COM+ 구성 요소에 동일하게 적용됩니다.

해당하는 경우 개체 풀링 및 JIT 활성화 사용

위의 테스트 결과가 보여주듯이 메서드 호출이 구성 요소 활성화보다 빠르며 관리되지 않는 구성 요소의 활성화가 Enterprise Services 구성 요소의 활성화보다 빠릅니다. 따라서 구성 요소 기반 응용 프로그램의 속도를 최대한 높이려면 코드에서 구성 요소 활성화 및 삭제의 수를 최소화하는 것이 중요합니다.

COM+에서는 개체 활성화를 최소화할 수 있는 두 가지 서비스를 제공합니다.

  1. 첫 번째는 앞에서 설명했듯이 호출자가 개체에 대한 활성 참조를 보관하고 있는 동안 해당 개체를 원활하게 비활성화할 수 있는 COM+ 서비스인 JIT(Just-in-time) 활성화입니다. 클라이언트가 개체에 메서드를 호출하기만 하면 COM+가 개체의 할당을 동적으로 관리하여 요청을 처리합니다.
  2. 두 번째는 해당 형식의 구성 요소 인스턴스를 요청하는 클라이언트가 바로 사용할 수 있도록 개체를 풀에서 활성 상태로 유지하는 개체 풀링입니다. COM+는 풀을 자동으로 관리하며, 개체 활성화 정보를 처리하고 사용자가 지정한 기준(예: 풀 크기)에 따라 다시 사용합니다.

풀링된 구성 요소 및 JIT 활성화된 구성 요소에 대한 참조를 보관하고 다시 사용함으로써 구성 요소 활성화 및 삭제를 최소화하고 높은 수준의 성능을 얻을 수 있습니다.

COM+ JIT 활성화 및 개체 풀링에 대한 자세한 내용은 Platform SDK: COM+ (Component Services) 설명서 를 참조하십시오.

왕복 회피

COM+ 구성 요소의 성능을 최적화하려면 호출자와 구성 요소 사이에 수행되는 프로세스 간 또는 컴퓨터 간 호출의 수를 최소화하는 것이 중요합니다. COM+ 구성 요소에서 만들어진 모든 메서드는 프로세스 간 호출 전환은 물론 컴퓨터 간 호출 전환도 초래하며, 매번 전환할 때마다 시간이 걸립니다. 따라서 COM+ 개체에서 만들어지는 메서드 호출을 최소한으로 유지하는 것이 필수적입니다. 그러기 위해 단일 호출에서 최대한 많은 작업을 수행하는 메서드를 사용하여 COM+ 구성 요소를 디자인하는 것도 좋은 방법입니다. 단, 이 경우에는 순수 아키텍처에서 변형된 구성 요소도 디자인해야 합니다.

COM+ 서비스 사용 최적화

COM+가 중요한 서비스를 다수 제공하기는 하지만 이러한 서비스를 현명하게 사용하는 것이 중요합니다. COM+가 구성 요소에 필요한 서비스를 제공하는 경우에는 대개 이 서비스가 가장 높은 성능을 제공하므로 반드시 사용하도록 하십시오. 그러나 서비스가 필요하지 않은 경우에는 구성 요소가 불필요한 작업을 수행하여 실행 속도도 느려질 수 있으므로 해당 서비스를 사용할 필요가 없습니다.

COM 마샬링 가능 매개 변수 사용

호출자가 데이터를 전달할 때 사용하는 매개 변수를 Enterprise Services 구성 요소의 메서드가 받아들이는 경우 다음과 같이 COM과 .NET 사이에 쉽게 마샬링할 수 있는 형식을 사용할 것을 매우 강력히 제안합니다.

  • Boolean
  • Byte, SByte
  • Char
  • DateTime
  • Decimal
  • Single, Double
  • Guid
  • Int16, UInt16, Int32, UInt32, Int64, UInt64
  • IntPtr, UIntPtr
  • String

이러한 형식만 사용하고 다른 복잡한 형식(예: 구조 또는 배열)은 전달하지 않으면 .NET serializer가 호출 처리 스택을 최적화하고 호출을 유선(RPC의 경우) 또는 가상 유선(LRPC의 경우)에 일렬로 직렬화할 수 있습니다. 그러면 호출이 더욱 빨리 실행됩니다. 그러나 메서드에 복잡한 형식이 필요한 경우에는 코드가 일반 DCOM 호출 스택을 통해 호출되므로 추가 처리 과정이 발생합니다.

파이널라이저 사용하지 않기

Enterprise Services 구성 요소에서 파이널라이저(C# 및 C++의 ~Classname() 소멸자)를 구현하지 마십시오. 파이널라이제이션은 가비지 수집기의 단일 스레드 작업입니다. Enterprise 구성 요소를 파이널라이제이션할 경우 완료되는 데 상당한 시간이 걸리므로 가비지 수집기 성능이 저하됩니다. 가비지 수집기 엔진이 실행 중인 동안에는 응용 프로그램에서 다른 작업을 수행할 수 없으므로 가비지 수집기가 완료되는 데 오랜 시간이 걸리면 전체 응용 프로그램의 성능이 저하됩니다.

대신 개체에서 Dispose(bool)을 재정의하고 Dispose(true)가 호출될 때 파이널라이제이션 형식 작업을 수행하는 것을 고려해 보십시오. 또한 이러한 종료 코드를 최대한 정돈되고, 안전하고, 간단하게 유지하도록 하십시오.

단일 스레드 COM+ 구성 요소 만들지 않기

여러 스레드의 동시 액세스를 지원하지 않는 개체는 STA(단일 스레드 아파트) 기능을 지원하는 것으로 표시되어 있습니다. 여러 스레드가 같은 인스턴스에 동시 액세스할 수 있도록 지원하는 구성 요소는 MTA(다중 스레드 아파트) 인식으로 표시되어 있습니다.

.NET Enterprise Services 구성 요소는 항상 STA 및 MTA를 모두 지원하는 것으로 표시되어 있기 때문에 더 이상 이 논의에 포함시키지 않겠습니다.

모든 Visual Basic 6 COM+ 구성 요소는 STA입니다. C++ COM+ 개발자는 구성 요소를 STA, MTA 또는 둘 다로 표시할 수 있습니다.

STA COM+ 구성 요소의 잠재적인 문제는 개체가 단일 스레드에서만 실행될 수 있으므로 해당 스레드가 개체의 메서드를 실행하는 유일한 스레드라는 것입니다. 이 직렬화를 통해 개발자는 STA 구성 요소를 더욱 쉽게 작성할 수 있지만, 도메인 간 마샬링이 종종 필요해서 성능이 저하되고 STA에서 한 스레드만 계속 실행됨으로 인해 확장성이 떨어지는 것은 피할 수 없습니다.

가능하면 STA 구성 요소를 만들거나 사용하지 않을 것을 제안합니다. 특히 확장성이 중요한 곳에서는 더욱 그렇습니다. 구성 요소가 다른 COM+ 구성 요소를 호출하는 경우 특히 STA 스레딩을 피해야 합니다. 이러한 호출에는 종종 스레드 전환이 필요한데, 스레드 전환은 해당 아파트에 있는 다른 모든 COM+ 구성 요소를 차단합니다.

설상가상으로 가비지 수집기의 파이널라이저도 개체를 소유한 STA 스레드를 호출할 때 차단됩니다. 그러면 파이널라이제이션 프로세스가 단일 스레드에 직렬화되며, 이는 이전 주제에서 설명했듯이 시스템 성능을 크게 저하시킬 수 있습니다.

부록 2: "Indigo" 및 .NET의 동향

Microsoft에서 현재 개발 중인 연결된 응용 프로그램을 위한 새로운 플랫폼, 코드 이름 "Indigo"에 대해 들어 보셨을 것입니다. 그렇다면 "Indigo"란 과연 어떤 제품일까요?

"Indigo"는 서비스 지향 연결 응용 프로그램을 위한 Microsoft의 전략적 기술 플랫폼으로서, 로스앤젤레스에서 개최된 2003 PDC(Professional Developers Conference)에서 소개되었습니다.

"Indigo"는 다음 기술의 개념, 특징 및 기능을 하나의 기술 스택으로 통합합니다.

  • COM
  • DCOM
  • COM+/Enterprise Services
  • ASMX/Web Services
  • .NET Remoting
  • 향상된 웹 서비스
  • MSMQ의 요소

"Indigo"는 서비스를 호출자에게 노출하는 데 필요한 프로토콜 및 전송으로부터 서비스의 개념을 추상화하는 다계층 플랫폼입니다. 최대한 많은 시스템과 상호 작동하기 위해 "Indigo"는 HTTP, TCP 및 IPC를 통한 고급 웹 서비스(WS-*)를 완벽하게 지원합니다. 현재 Microsoft는 코드 이름이 "Longhorn"인 Microsoft Windows의 출시 시기에 맞춰 "Indigo"를 배포할 예정입니다. Windows XP 및 Windows Server 2003에 대한 "Indigo" 지원도 함께 배포될 것입니다.

향후 "Indigo"가 발표된다고 하니 .NET Enterprise Services(또는 이 경우 ASMX 및 Remoting) 같은 기존 기술이 오늘날의 연결된 응용 프로그램을 개발하는 데 아직 유효한 것인지 걱정할 수도 있습니다. ASMX & WSE, Enterprise Services, Remoting 및 MSMQ는 오늘날의 엔터프라이즈 수준 솔루션에 가장 적합한 기술입니다. 적절하게 사용하면 "Indigo"가 릴리스되고 널리 사용될 때까지 훌륭한 응용 프로그램 개발 플랫폼을 제공해 줄 것입니다.

.NET을 사용하여 새 응용 프로그램을 작성하고 기존 응용 프로그램을 .NET으로 마이그레이션하면 향상된 보안, 안정성, 관리 및 확장성의 혜택을 누릴 수 있습니다. 또한 "Indigo"로 업그레이드하는 것이 원시 코드보다 훨씬 쉬워질 것입니다. 응용 프로그램의 "Indigo" 업그레이드를 준비하는 방법과 기존 기술을 사용하여 "Indigo"와 상호 작동하는 방법에 대한 자세한 내용은 나중에 MSDN을 참조하십시오.

"Indigo"에 대한 자세한 소개는 MSDN Magazine 기사, Code Name Indigo: A Guide to Developing and Running Connected Systems with Indigo 를 참조하십시오. 이 기사에 제공된 아키텍처 개요를 통해 이후의 Microsoft 응용 프로그램 플랫폼을 미리 살펴볼 수 있습니다.

"Indigo"에 대한 일반적인 내용은 Microsoft "Indigo" Frequent叿䉍/�᠀젇������ࠅက抅.�ⷞˀĀ＀�ÿ�Ā�䀀��Āc="/korea/msdn/images/tous.gif" border=0>를 참조하십시오.

부록 3: 분산 트랜잭션이 성능에 미치는 영향

위 테스트를 진행하면서 "COM+ 분산 트랜잭션이 이러한 구성 요소에 얼마나 많은 영향을 미칠까?"라는 질문이 생길 수도 있습니다. 이 질문의 답을 찾기 위해 각 구성 요소에 대해 COM+에서 "트랜잭션 필요" 설정을 끄고 테스트를 다시 실행해 보았습니다. 그 결과는 다음 차트에 표시되어 있습니다.

위 차트에서 볼 수 있듯이 COM+ 트랜잭션 지원이 없는 구성 요소의 성능과 트랜잭션이 켜져 있는 구성 요소의 성능은 거의 동일합니다. 이 결과는 테스트에서 COM+ 트랜잭션의 영향을 무시해도 상관없다는 것을 분명하게 보여 줍니다.

부록 4: 참고 자료

Upgrading Microsoft Visual Basic 6.0 to Microsoft Visual Basic .NET 

Visual Basic 6.0 응용 프로그램을 Visual Basic .NET으로 업그레이드하는 내용을 다루며 프로그래밍 팁, 요령 및 단계별 코드 비교 내용을 함께 제공합니다.

Programming with Managed Extensions for Microsoft Visual C++ .NET 

Visual C++ .NET 2003에 맞춰 업데이트된 이 책은 개발자를 위해 컴파일러의 새 기능과 언어에 대한 링커 확장을 심층적이고 전문적으로 다룹니다.

.NET Enterprise Services and COM+ 1.5 Architecture 

Microsoft .NET 및 Enterprise Services가 어떻게 조화를 이루는지 보여 주고 COM+/Enterprise Services 구성 요소를 빌드, 제어, 관리 및 보안하는 방법을 설명합니다.

.NET Framework Developer Center의 Performance 페이지 

고성능 코드를 작성하는 방법과 발생하는 문제를 진단하는 방법에 대해 심도있게 다루는 여러 링크와 리소스가 제공됩니다.

Performance Tips and Tricks in .NET Applications 

.NET 응용 프로그램이 제대로 수행되도록 하기 위한 팁과 힌트를 모아 놓았습니다.

Writing Faster Managed Code: Know What Things Cost 

.NET 코드에서 다양한 작업이 시스템에 어떤 부담을 주는지 자세히 분석한 내용입니다.

Garbage Collector Basics and Performance Hints 

가비지 수집기의 작동 방법, 가비지 수집기가 코드에 미치는 영향, 가비지 수집의 영향을 최소화하도록 코드를 작성하는 방법 등에 대해 설명합니다.

Performance Considerations for Run-Time Technologies in the .NET Framework 

가비지 수집 및 메모리 사용, JIT, 스레딩, .NET Remoting, 보안 등의 주제를 다룹니다.

부록 5: 성능 테스트 소스 코드

C++\ATL 구성 요소

헤더 파일

// ATLPerfTestObj.h : CATLPerfTestObj의 선언

#pragma once
#include "ATLPerfTests.h"
#include "resource.h" // 주 기호입니다.
#include <comsvcs.h>
#include <mtxattr.h>


// CATLPerfTestObj

class ATL_NO_VTABLE CATLPerfTestObj :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CATLPerfTestObj, &CLSID_ATLPerfTestObj>,
public IDispatchImpl<IPerfTestObj, &IID_IPerfTestObj, &LIBID_ATLPerfTestsLib,
/*wMajor =*/ 1, /*wMinor =*/ 0>
{
public :
CATLPerfTestObj()
{
}

DECLARE_PROTECT_FINAL_CONSTRUCT()

HRESULT FinalConstruct()
{
return S_OK;
}

void FinalRelease()
{
}

DECLARE_REGISTRY_RESOURCEID(IDR_ATLPERFTESTOBJ)

DECLARE_NOT_AGGREGATABLE(CATLPerfTestObj)

BEGIN_COM_MAP(CATLPerfTestObj)
COM_INTERFACE_ENTRY(IPerfTestObj)
COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()


// IPerfTestObj
public :
STDMETHOD(Sum)(LONG number1, LONG number2, LONG* result);
STDMETHOD(AddSale)(LONG orderNumber, LONG storeID, LONG titleID,
LONG qty);

private :
HRESULT InsertSaleRecord(LONG orderNumber, LONG storeID, LONG
titleID, LONG qty);

};

OBJECT_ENTRY_AUTO(__uuidof(ATLPerfTestObj), CATLPerfTestObj)(참고: 프로그래머 코멘트는 샘플 프로그램 파일에는 영문으로 제공되며 기사에는 설명을 위해 번역문으로 제공됩니다.)

소스 파일

// ATLPerfTestObj.cpp : CATLPerfTestObj의 구현

#include "stdafx.h"
#include "ATLPerfTestObj.h"
#include ".\atlperftestobj.h"

#include "atlstr.h"

#import "c:\Program Files\Common Files\System\ADO\msado15.dll" rename_namespace("ADO")
rename("EOF", "EndOfFile")
using namespace ADO;

// CATLPerfTestObj

// 여기에서 간단한 작업을 수행하여 간단한 메서드를 시뮬레이트합니다.
STDMETHODIMP CATLPerfTestObj::Sum(LONG number1, LONG number2, LONG* result)
{
// 개체 컨텍스트를 가져옵니다.
IObjectContext* ctx = NULL;
HRESULT hr = CoGetObjectContext(IID_IObjectContext,
(LPVOID*)&ctx);
if(SUCCEEDED(hr))
{
// 계산을 수행합니다.
*result = number1 + number2;

// 트랜잭션을 커밋합니다.
ctx->SetComplete();
ctx->Release();
}

return hr;
}

STDMETHODIMP CATLPerfTestObj::AddSale(LONG orderNumber, LONG storeID, LONG titleID, LONG qty)
{
// COM+ 개체 컨텍스트를 가져옵니다.
IObjectContext* ctx = NULL;
HRESULT hr = CoGetObjectContext(IID_IObjectContext, (LPVOID*)&ctx);

// COM+ 컨텍스트가 있는지 확인합니다.
if(SUCCEEDED(hr))
{
// 기본 작업은 중단하는 것입니다.
ctx->SetAbort();

// 데이터베이스에 레코드를 삽입합니다.
hr = InsertSaleRecord(orderNumber, storeID, titleID, qty);

// 삽입 작업의 결과를 확인합니다.
if(SUCCEEDED(hr))
{
// 모든 것이 올바르면 트랜잭션을 완료로
// 표시합니다.
ctx->SetComplete();
}

// 컨텍스트 포인터를 정리합니다.
ctx->Release();
ctx = NULL;
}

// 전체 결과를 반환합니다. 삽입 작업이 오류 없이
// 실행되는 경우에만 결과가 S_OK입니다.
return hr;
}

// 데이터베이스에 판매 레코드를 삽입합니다.
HRESULT CATLPerfTestObj::InsertSaleRecord(LONG orderNumber, LONG storeID,
LONG titleID, LONG qty)
{
// 기본 결과는 실패를 반환하는 것입니다.
HRESULT hr = E_FAIL;

try
{
// 서버에서 실행할 SQL을 포맷합니다.
CString str;
str.Format("insert into sales (order_no, store_id, \
title_id, order_date, qty) values (%i, %i, %i, \
GetDate(), %i)",
orderNumber, storeID, titleID, qty);

// 데이터베이스에 대한 연결을 엽니다.
_ConnectionPtr cn("ADODB.Connection");
cn->Open("Provider=SQLOLEDB;SERVER=localhost;Integrated \
Security=SSPI;DATABASE=ESPERFTESTDB",
"", "", adConnectUnspecified);

// 명령을 실행합니다.
_variant_t rs;
rs = cn->Execute(_bstr_t(str), &rs, adCmdText);
hr = S_OK;

// 연결을 명시적으로 닫습니다.
cn->Close();
}
catch(_com_error e)
{
// 문제가 있으면 오류가 반환됩니다.
hr = e.Error();
}

return hr;
}

Visual Basic 6 구성 요소

Private Function IPerfTestObj_Sum(ByVal nA As Long, ByVal nB As Long) As Long
IPerfTestObj_Sum = nA + nB

GetObjectContext.SetComplete
End Function

Sub IPerfTestObj_AddSale(ByVal OrderNumber As Long, ByVal StoreID As Long,
ByVal TitleID As Long, ByVal Qty As Long)
' 기본값은 오류가 throw될 경우 중단하는 것입니다.
GetObjectContext.SetAbort

Call InsertSaleRecord(OrderNumber, StoreID, TitleID, Qty)

' 실패하지 않았기 때문에 트랜잭션을 완료할 수 있습니다.
GetObjectContext.SetComplete
End Sub

Sub InsertSaleRecord(ByVal OrderNumber As Long, ByVal StoreID As Long,
ByVal TitleID As Long, ByVal Qty As Long)
Dim command As String

connDB.Open ("Provider=SQLOLEDB;SERVER=localhost;Integrated" + _
"Security=SSPI;DATABASE=ESPERFTESTDB")

command = "insert into sales (store_id, order_no, order_date, qty, title_id)
values (" & StoreID & ", " & OrderNumber & ", GetDate(),
" & Qty & ", " & TitleID & ")"

connDB.Execute (command)
connDB.Close
End Sub

C# 구성 요소

using System;
using System.EnterpriseServices;
using System.Data.SqlClient;
using System.Reflection;
using System.Runtime.InteropServices;

using perftestsinterop;

[assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyKeyFile("..\\..\\sign.key")]
[assembly: ApplicationName("PerfTest")]
[assembly: ApplicationActivation(ActivationOption.Server)]
[assembly: ApplicationAccessControl(false)]

namespace CSPerfTests
{
[Guid("0BA5534E-8544-42e2-A909-3265105BEA09")]
[Transaction(TransactionOption.Required)]
public class CSPerfTestObj : ServicedComponent, IPerfTestObj
{
public CSPerfTestObj()
{
}

// 여기에서 간단한 작업을 수행하여 간단한 메서드를
// 시뮬레이트합니다.
public int Sum(int number1, int number2)
{
int result = 0;

// 계산을 수행합니다.
result = number1 + number2;

// 트랜잭션을 커밋합니다.
ContextUtil.SetComplete();

return result;
}

// 트랜잭션에 새 판매 항목을 추가합니다.
public void AddSale(int orderNumber, int storeID, int
titleID, int qty)
{
try
{
// 데이터베이스에 레코드를 삽입합니다.
InsertSaleRecord(orderNumber, storeID, titleID,
qty);

// 모든 것이 올바르면 트랜잭션을 완료로
// 표시합니다.
ContextUtil.SetComplete();
}
catch(Exception)
{
ContextUtil.SetAbort();
throw;
}
}

// 데이터베이스에 판매 레코드를 삽입합니다.
private void InsertSaleRecord(int orderNumber, int storeID,
int titleID, int qty)
{
// 서버에서 실행할 SQL을
// 포맷합니다.
string commandStr = string.Format("insert into sales\
(order_no, store_id, title_id, order_date, qty) \
values ({0}, {1}, {2}, GetDate(), {3})",
orderNumber, storeID, titleID, qty);

// 데이터베이스에 대한 연결을 엽니다.
SqlConnection connection = new
SqlConnection("SERVER=localhost;Integrated
Security=SSPI;DATABASE=ESPERFTESTDB");
connection.Open();

// 명령을 실행합니다.
SqlCommand cmd = new SqlCommand(commandStr,
connection);
cmd.ExecuteNonQuery();

// 연결을 명시적으로 닫습니다.
connection.Close();
}
}
}

Visual Basic .NET 구성 요소

Imports System
Imports System.EnterpriseServices
Imports System.Data.SqlClient
Imports System.Reflection
Imports System.Runtime.InteropServices

Imports perftestsinterop

<Assembly: AssemblyVersion("1.0.0")>
<Assembly: AssemblyKeyFile("..\..\sign.key")>
<Assembly: ApplicationName("PerfTest")>
<Assembly: ApplicationActivation(ActivationOption.Server)>
<Assembly: ApplicationAccessControl(False)>

<Guid("53400EEB-E8C1-4D15-81D4-AFAC896B34FA"), _
Transaction(TransactionOption.Required)> _
Public Class VBPerfTestObj
Inherits ServicedComponent
Implements IPerfTestObj

'-- 여기에서 간단한 작업을 수행하여 간단한 메서드를
'-- 시뮬레이트합니다.
Public Function Sum(ByVal number1 As Integer, ByVal number2 As
Integer) As Integer _
Implements IPerfTestObj.Sum
Dim result As Integer = 0

'-- 계산을 수행합니다.
result = number1 + number2

'-- 트랜잭션을 커밋합니다.
ContextUtil.SetComplete()

Return result
End Function

'-- 트랜잭션에 새 판매 항목을 추가합니다.
Public Sub AddSale(ByVal orderNumber As Integer, ByVal storeID As
Integer, ByVal titleID As Integer, ByVal qty As Integer) _
Implements IPerfTestObj.AddSale
Try
'-- 데이터베이스에 레코드를 삽입합니다.
InsertSaleRecord(orderNumber, storeID, titleID, qty)

'-- 모든 것이 올바르면 트랜잭션을 완료로 표시합니다.
ContextUtil.SetComplete()

Catch
ContextUtil.SetAbort()
Throw
End Try

End Sub

'-- 데이터베이스에 판매 레코드를 삽입합니다.
Private Sub InsertSaleRecord(ByVal orderNumber As Integer, ByVal
storeID As Integer, ByVal titleID As Integer, ByVal qty As
Integer)
'-- 서버에서 실행할 SQL을 포맷합니다.
Dim commandStr As String
commandStr = String.Format("insert into sales (order_no," + _
"store_id, title_id, order_date, qty) values ({0}, {1}, _
{2}, GetDate(), {3})", _
orderNumber, storeID, titleID, qty)

'-- 데이터베이스에 대한 연결을 엽니다.
Dim connection As SqlConnection = New
SqlConnection("SERVER=localhost;Integrated
Security=SSPI;DATABASE=ESPERFTESTDB")
connection.Open()

'-- 명령을 실행합니다.
Dim cmd As SqlCommand = New SqlCommand(commandStr, connection)
cmd.ExecuteNonQuery()

'-- 연결을 명시적으로 닫습니다.
connection.Close()
End Sub

End Class

테스트 응용 프로그램

using System;
using System.Reflection;
using System.Diagnostics;
using System.Threading;
using System.Runtime.InteropServices;
using System.EnterpriseServices;
using System.Text;
using System.IO;
using COMAdmin;

using ATLPerfTestsLib;
using CSPerfTests;
using VBPerfTests;

[assembly: AssemblyVersion("1.0.*")]

namespace TestApp
{
[Flags]
enum TestType
{
NoOp = 0x0,
LightMethod = 0x1,
HeavyMethod = 0x2,
Activations = 0x40000000,

ActivateLight = Activations | LightMethod,
ActivateHeavy = Activations | HeavyMethod,

}

public class App
{
public static HighResolutionTimer Timer;
public static int RunID = 0;
public static TextWriter outputFile;

[STAThread]
public static void Main(string[] args)
{
Timer = new HighResolutionTimer();
RunID = (int)Timer.TickCount;

Console.WriteLine("\a");

outputFile = new StreamWriter(new
FileStream("results.csv", FileMode.Create,
FileAccess.Write), Encoding.UTF8);

Type t;
long iterations = 5000;

// 빈 활성화 및 기본 삭제를 테스트합니다.
outputFile.WriteLine("Test Type, Number of
Iterations, Object Type, Elapsed Time (sec),
Operations/sec");

t = typeof(ATLPerfTestObjClass);
TestActivations(t, iterations, TestType.Activations,
ApartmentState.MTA);

t = Type.GetTypeFromProgID("VB6Perf.HPerf");
TestActivations(t, iterations, TestType.Activations,
ApartmentState.STA);

t = typeof(CSPerfTestObj);
TestActivations(t, iterations, TestType.Activations,
ApartmentState.MTA);

t = typeof(VBPerfTestObj);
TestActivations(t, iterations, TestType.Activations,
ApartmentState.MTA);


t = typeof(ATLPerfTestObjClass);
TestActivations(t, iterations,
TestType.ActivateLight, ApartmentState.MTA);

t = Type.GetTypeFromProgID("VB6Perf.HPerf");
TestActivations(t, iterations,
TestType.ActivateLight, ApartmentState.STA);

t = typeof(CSPerfTestObj);
TestActivations(t, iterations,
TestType.ActivateLight, ApartmentState.MTA);

t = typeof(VBPerfTestObj);
TestActivations(t, iterations,
TestType.ActivateLight, ApartmentState.MTA);

t = typeof(ATLPerfTestObjClass);
TestActivations(t, iterations,
TestType.ActivateHeavy, ApartmentState.MTA);

t = Type.GetTypeFromProgID("VB6Perf.HPerf");
TestActivations(t, iterations,
TestType.ActivateHeavy, ApartmentState.STA);

t = typeof(CSPerfTestObj);
TestActivations(t, iterations,
TestType.ActivateHeavy, ApartmentState.MTA);

t = typeof(VBPerfTestObj);
TestActivations(t, iterations,
TestType.ActivateHeavy, ApartmentState.MTA);

// 올바른 Trivial Method Test 숫자를
// 가져오기 위해 두 개의 확대 명령으로
// 반복 수를 늘립니다.
t = typeof(ATLPerfTestObjClass);
TestActivations(t, iterations*100,
TestType.LightMethod, ApartmentState.MTA);

t = Type.GetTypeFromProgID("VB6Perf.HPerf");
TestActivations(t, iterations*100,
TestType.LightMethod, ApartmentState.STA);

t = typeof(CSPerfTestObj);
TestActivations(t, iterations*100,
TestType.LightMethod, ApartmentState.MTA);

t = typeof(VBPerfTestObj);
TestActivations(t, iterations*100,
TestType.LightMethod, ApartmentState.MTA);

t = typeof(ATLPerfTestObjClass);
TestActivations(t, iterations, TestType.HeavyMethod,
ApartmentState.MTA);

t = Type.GetTypeFromProgID("VB6Perf.HPerf");
TestActivations(t, iterations, TestType.HeavyMethod,
ApartmentState.STA);

t = typeof(CSPerfTestObj);
TestActivations(t, iterations, TestType.HeavyMethod,
ApartmentState.MTA);

t = typeof(VBPerfTestObj);
TestActivations(t, iterations, TestType.HeavyMethod,
ApartmentState.MTA);

outputFile.Close();

Console.WriteLine("\a");
}

private static void TestActivations(Type type, long
iterations, TestType test, ApartmentState aptype)
{
// 새 스레드를 만들어 실행 중인 스레드의
// 아파트 형식을 제어합니다.
TestRunner runner = new TestRunner(type, iterations,
test);
Thread thread = new Thread(new
ThreadStart(runner.Run));
thread.ApartmentState = aptype;
thread.Start();
thread.Join();
COMAdminCatalog catalog = new COMAdminCatalogClass();
catalog.ShutdownApplication("PerfTest");
}

class TestRunner
{
Type type;
long iterations;
TestType test;

string testTypeName;
string componentTypeName;

public TestRunner(Type type, long iterations,
TestType test)
{
this.type = type;
this.iterations = iterations;
this.test = test;

// 나중에 사용할 테스트 형식 및
// 구성 요소 형식 이름 문자열을 만듭니다.
switch(test)
{
case TestType.Activations:
testTypeName = "Activations, ";
break;

case TestType.ActivateLight:
testTypeName = "Activations with \
Light Method Call, ";
break;

case TestType.ActivateHeavy:
testTypeName = "Activations with \
Heavy Method Call, ";
break;

case TestType.LightMethod:
testTypeName = "Light Method \
Calls, ";
break;

case TestType.HeavyMethod:
testTypeName = "Heavy Method \
Calls, ";
break;
};

if (type == typeof(ATLPerfTestObjClass))
componentTypeName = "C++/ATL, ";
else if(type == typeof(CSPerfTestObj))
componentTypeName = "C#, ";
else if(type == typeof(VBPerfTestObj))
componentTypeName = "VB.NET, ";
else
componentTypeName = "VB6, ";
}

void TestLoop(long iterations)
{
bool timingActivations = (this.test &
TestType.Activations) != 0;
perftestsinterop.IPerfTestObj o = null;

// 활성화에 시간을 지정하지 않은 경우
// 루프 밖에서 지정합니다.
if(!timingActivations)
{
o = (perftestsinterop.IPerfTestObj)
Activator.CreateInstance(type);
}

// 테스트를 수행합니다.
for (int i = 0; i < iterations; i++)
{
if(timingActivations)
{
o = (perftestsinterop.IPerfTestObj)
Activator.CreateInstance(type);
}

// 수행해야 하는 테스트를
// 찾아내어 수행합니다. 활성화 비트를
// 제거하여 실행하고자 하는
// 종류의 메서드만 남아 있도록
// 합니다.
switch(test & ~TestType.Activations)
{
case TestType.NoOp:
break;

case TestType.LightMethod:
o.Sum(100, 200);
break;

case TestType.HeavyMethod:
int count =
(int)Timer.TickCount;
o.AddSale(RunID, count, count
% 10000, count % 10);
break;
};

if(timingActivations)
{
// 지금 개체를 삭제합니다.
// 작업이 완료되었습니다.
if (type.IsCOMObject)
{
Marshal.ReleaseComObject(o);
}
else
{
ServicedComponent.DisposeObject((ServicedComponent)o);
}
}
}
}

public void Run()
{
float elapsedTime = 0.0f;

// 실행을 준비합니다.
TestLoop(10);

// 타이머를 중지하고 다시 설정합니다.
Timer.Stop();
Timer.Reset();
Timer.Start();

TestLoop(iterations);

// 루프가 끝나면 타이머를 중지하고
// 경과 시간을 반환합니다.
elapsedTime = Timer.ElapsedTime;
Timer.Stop();

// 결과를 출력합니다.
StringBuilder sb = new StringBuilder();
sb.Append(testTypeName);
sb.Append(iterations.ToString());
sb.Append(", ");
sb.Append(componentTypeName);
sb.Append(elapsedTime.ToString());
sb.Append(", ");
sb.Append((iterations/elapsedTime).ToString());
outputFile.WriteLine(sb.ToString());

GC.WaitForPendingFinalizers();
}
}

}
}

부록 6: 테스트 결과

TX 범위 JITA 메서드 형식 개체 형식 연산/초
Tx 프로세스 내 JITA 없음 메서드 없음 C++/ATL 2846.9
Tx 프로세스 내 JITA 없음 메서드 없음 Visual Basic 6 1186.2
Tx 프로세스 내 JITA 없음 메서드 없음 C# 1581.7
Tx 프로세스 내 JITA 없음 메서드 없음 Visual Basic .NET 1581.6
Tx 프로세스 내 JITA 없음 간단한 메서드 C++/ATL 2588.1
Tx 프로세스 내 JITA 없음 간단한 메서드 Visual Basic 6 1138.8
Tx 프로세스 내 JITA 없음 간단한 메서드 C# 1423.8
Tx 프로세스 내 JITA 없음 간단한 메서드 Visual Basic .NET 1423.5
Tx 프로세스 내 JITA 없음 일반 메서드 C++/ATL 243.3
Tx 프로세스 내 JITA 없음 일반 메서드 Visual Basic 6 243.3
Tx 프로세스 내 JITA 없음 일반 메서드 Visual Basic .NET 245.4
Tx 프로세스 내 JITA 없음 일반 메서드 C# 245.4
Tx 프로세스 내 JITA 간단한 메서드 C++/ATL 49084.6
Tx 프로세스 내 JITA 간단한 메서드 Visual Basic 6 29655.3
Tx 프로세스 내 JITA 간단한 메서드 C# 12218.5
Tx 프로세스 내 JITA 간단한 메서드 Visual Basic .NET 12166.3
Tx 프로세스 내 JITA 일반 메서드 C++/ATL 245.4
Tx 프로세스 내 JITA 일반 메서드 Visual Basic 6 247.6
Tx 프로세스 내 JITA 일반 메서드 C# 247.6
Tx 프로세스 내 JITA 일반 메서드 Visual Basic .NET 245.4
Tx 프로세스 외 JITA 없음 메서드 없음 C++/ATL 351.5
Tx 프로세스 외 JITA 없음 메서드 없음 Visual Basic 6 237.2
Tx 프로세스 외 JITA 없음 메서드 없음 C# 176.8
Tx 프로세스 외 JITA 없음 메서드 없음 Visual Basic .NET 176.8
Tx 프로세스 외 JITA 없음 간단한 메서드 C++/ATL 261.2
Tx 프로세스 외 JITA 없음 간단한 메서드 Visual Basic 6 192.4
Tx 프로세스 외 JITA 없음 간단한 메서드 C# 124.3
Tx 프로세스 외 JITA 없음 간단한 메서드 Visual Basic .NET 123.8
Tx 프로세스 외 JITA 없음 일반 메서드 C++/ATL 123.2
Tx 프로세스 외 JITA 없음 일반 메서드 Visual Basic 6 121.7
Tx 프로세스 외 JITA 없음 일반 메서드 Visual Basic .NET 80.0
Tx 프로세스 외 JITA 없음 일반 메서드 C# 79.3
Tx 프로세스 외 JITA 간단한 메서드 C++/ATL 16456.1
Tx 프로세스 외 JITA 간단한 메서드 Visual Basic 6 8600.9
Tx 프로세스 외 JITA 간단한 메서드 C# 7299.8
Tx 프로세스 외 JITA 간단한 메서드 Visual Basic .NET 7281.1
Tx 프로세스 외 JITA 일반 메서드 C++/ATL 243.3
Tx 프로세스 외 JITA 일반 메서드 Visual Basic 6 245.4
Tx 프로세스 외 JITA 일반 메서드 C# 243.3
Tx 프로세스 외 JITA 일반 메서드 Visual Basic .NET 245.4
Tx 컴퓨터간 JITA 없음 메서드 없음 C++/ATL 142.8
Tx 컴퓨터간 JITA 없음 메서드 없음 Visual Basic 6 93.5
Tx 컴퓨터간 JITA 없음 메서드 없음 C# 100.7
Tx 컴퓨터간 JITA 없음 메서드 없음 Visual Basic .NET 100.7
Tx 컴퓨터간 JITA 없음 간단한 메서드 C++/ATL 117.1
Tx 컴퓨터간 JITA 없음 간단한 메서드 Visual Basic 6 80.2
Tx 컴퓨터간 JITA 없음 간단한 메서드 C# 72.8
Tx 컴퓨터간 JITA 없음 간단한 메서드 Visual Basic .NET 73.0
Tx 컴퓨터간 JITA 없음 일반 메서드 C++/ATL 81.7
Tx 컴퓨터간 JITA 없음 일반 메서드 Visual Basic 6 61.7
Tx 컴퓨터간 JITA 없음 일반 메서드 C# 48.0
Tx 컴퓨터간 JITA 없음 일반 메서드 Visual Basic .NET 48.9
Tx 컴퓨터간 JITA 간단한 메서드 C++/ATL 1902.7
Tx 컴퓨터간 JITA 간단한 메서드 Visual Basic 6 1672.5
Tx 컴퓨터간 JITA 간단한 메서드 C# 1573.3
Tx 컴퓨터간 JITA 간단한 메서드 Visual Basic .NET 1552.6
Tx 컴퓨터간 JITA 일반 메서드 C++/ATL 242.9
Tx 컴퓨터간 JITA 일반 메서드 Visual Basic 6 245.2
Tx 컴퓨터간 JITA 일반 메서드 C# 238.5
Tx 컴퓨터간 JITA 일반 메서드 Visual Basic .NET 238.5
Tx 없음 프로세스 내 JITA 없음 메서드 없음 C++/ATL 2695.3
Tx 없음 프로세스 내 JITA 없음 메서드 없음 Visual Basic 6 1207.0
Tx 없음 프로세스 내 JITA 없음 메서드 없음 C# 1664.7
Tx 없음 프로세스 내 JITA 없음 메서드 없음 Visual Basic .NET 1654.1
Tx 없음 프로세스 내 JITA 없음 간단한 메서드 C++/ATL 2586.3
Tx 없음 프로세스 내 JITA 없음 간단한 메서드 Visual Basic 6 1154.6
Tx 없음 프로세스 내 JITA 없음 간단한 메서드 C# 1517.1
Tx 없음 프로세스 내 JITA 없음 간단한 메서드 Visual Basic .NET 1507.0
Tx 없음 프로세스 내 JITA 없음 일반 메서드 C++/ATL 245.1
Tx 없음 프로세스 내 JITA 없음 일반 메서드 Visual Basic 6 245.4
Tx 없음 프로세스 내 JITA 없음 일반 메서드 C# 247.0
Tx 없음 프로세스 내 JITA 없음 일반 메서드 Visual Basic .NET 246.2
Tx 없음 프로세스 내 JITA 간단한 메서드 C++/ATL 50660.9
Tx 없음 프로세스 내 JITA 간단한 메서드 Visual Basic 6 31121.7
Tx 없음 프로세스 내 JITA 간단한 메서드 C# 12928.1
Tx 없음 프로세스 내 JITA 간단한 메서드 Visual Basic .NET 12808.6
Tx 없음 프로세스 내 JITA 일반 메서드 C++/ATL 247.4
Tx 없음 프로세스 내 JITA 일반 메서드 Visual Basic 6 246.2
Tx 없음 프로세스 내 JITA 일반 메서드 C# 247.4
Tx 없음 프로세스 내 JITA 일반 메서드 Visual Basic .NET 247.5
Tx 없음 프로세스 외 JITA 없음 메서드 없음 C++/ATL 327.0
Tx 없음 프로세스 외 JITA 없음 메서드 없음 Visual Basic 6 239.7
Tx 없음 프로세스 외 JITA 없음 메서드 없음 C# 177.4
Tx 없음 프로세스 외 JITA 없음 메서드 없음 Visual Basic .NET 177.9
Tx 없음 프로세스 외 JITA 없음 간단한 메서드 C++/ATL 259.3
Tx 없음 프로세스 외 JITA 없음 간단한 메서드 Visual Basic 6 192.2
Tx 없음 프로세스 외 JITA 없음 간단한 메서드 C# 125.1
Tx 없음 프로세스 외 JITA 없음 간단한 메서드 Visual Basic .NET 124.7
Tx 없음 프로세스 외 JITA 없음 일반 메서드 C++/ATL 123.3
Tx 없음 프로세스 외 JITA 없음 일반 메서드 Visual Basic 6 123.6
Tx 없음 프로세스 외 JITA 없음 일반 메서드 C# 82.6
Tx 없음 프로세스 외 JITA 없음 일반 메서드 Visual Basic .NET 82.6
Tx 없음 프로세스 외 JITA 간단한 메서드 C++/ATL 16637.9
Tx 없음 프로세스 외 JITA 간단한 메서드 Visual Basic 6 8847.9
Tx 없음 프로세스 외 JITA 간단한 메서드 C# 7345.1
Tx 없음 프로세스 외 JITA 간단한 메서드 Visual Basic .NET 7392.8
Tx 없음 프로세스 외 JITA 일반 메서드 C++/ATL 244.8
Tx 없음 프로세스 외 JITA 일반 메서드 Visual Basic 6 244.9
Tx 없음 프로세스 외 JITA 일반 메서드 C# 243.9
Tx 없음 프로세스 외 JITA 일반 메서드 Visual Basic .NET 245.5
Tx 없음 프로세스 외 JITA 없음 메서드 없음 C++/ATL 147.5
Tx 없음 컴퓨터간 JITA 없음 메서드 없음 Visual Basic 6 95.1
Tx 없음 컴퓨터간 JITA 없음 메서드 없음 C# 102.3
Tx 없음 컴퓨터간 JITA 없음 메서드 없음 Visual Basic .NET 101.7
Tx 없음 컴퓨터간 JITA 없음 간단한 메서드 C++/ATL 119.6
Tx 없음 컴퓨터간 JITA 없음 간단한 메서드 Visual Basic 6 81.6
Tx 없음 컴퓨터간 JITA 없음 간단한 메서드 C# 74.1
Tx 없음 컴퓨터간 JITA 없음 간단한 메서드 Visual Basic .NET 73.5
Tx 없음 컴퓨터간 JITA 없음 일반 메서드 C++/ATL 82.2
Tx 없음 컴퓨터간 JITA 없음 일반 메서드 Visual Basic 6 61.9
Tx 없음 컴퓨터간 JITA 없음 일반 메서드 C# 60.3
Tx 없음 컴퓨터간 JITA 없음 일반 메서드 Visual Basic .NET 61.2
Tx 없음 컴퓨터간 JITA 간단한 메서드 C++/ATL 1896.7
Tx 없음 컴퓨터간 JITA 간단한 메서드 Visual Basic 6 1620.5
Tx 없음 컴퓨터간 JITA 간단한 메서드 C# 1576.8
Tx 없음 컴퓨터간 JITA 간단한 메서드 Visual Basic .NET 1591.5
Tx 없음 컴퓨터간 JITA 일반 메서드 C++/ATL 242.7
Tx 없음 컴퓨터간 JITA 일반 메서드 Visual Basic 6 243.7
Tx 없음 컴퓨터간 JITA 일반 메서드 C# 245.8
Tx 없음 컴퓨터간 JITA 일반 메서드 Visual Basic .NET 246.0