Wednesday, November 16, 2005

MTS vs. COM+ From VB 6.0’s Perspective

Back in the days of Windows NT 4.0 there was a product called Microsoft Transaction Service (MTS) that allowed ActiveX/COM objects to execute on a separate server (i.e. DCOM) and allowed them to create distributed transactions. ActiveX/COM classes could be created using Visual Basic 6.0 or Visual C++ 6.0. It didn’t really matter which language was used, because they both ran and performed about the same.

In Windows 2000 Microsoft replaced MTS with Component Services (COM+). They were functionally equivalent (COM+ had some new features, but was backward compatible) and at that time Microsoft had released no documentation indicating that they were any different. However, as people started upgrading their Windows NT 4.0 servers to Windows 2000, they started seeing performance problems with their middle-tier components (objects that were running in MTS, but now were running in COM+), especially components written in VB 6.0 (but some VC++ 6.0 object were also having trouble).

It turns out that Microsoft made some major changes when they moved from MTS to COM+, and it was these changes that caused many n-tier applications to perform badly under Windows 2000/COM+. The following article explains the changes that were made and what can be done to restore the performance seen in Windows NT 4.0.

Background Information on COM Objects
COM makes use of a concept called apartments. An apartment is like a container that objects are placed in when they are created. Once an object is created and put into an apartment (or container), it cannot be moved into another apartment (or container). There are three kinds of apartments (or containers) in COM+: Single-threaded apartments (STAs), Multithreaded apartments (MTAs), and Thread-neutral apartments (TNAs). An STA always contains one and only one thread. All objects that are created and placed into an STA share the same execution thread. An error will be raised if another thread tries to execute or use the object. An MTA can contain many threads. An object created and placed into an MTA can be executed or manipulated on any of the threads that are assigned to that MTA. A TNA does not have any threads at all. This article only discusses STAs and MTAs.

All COM classes have a threading model. The threading model for a class is stored in the registry. The following chart, which comes from Tim Ewald’s book Transactional COM+: Building Scalable Applications, describes which apartment, based on its threading model registry setting, a COM object is placed in when it is created:

ThreadingModel API ValueThreadingModel Registry SettingApartment
COMAdminThreadingModelApartmentApartmentSTA
COMAdminThreadingModelFreeFreeMTA
COMAdminThreadingModelMain{none}Main STA
COMAdminThreadingModelBothBoth Either STA or MTA
COMAdminThreadingModelNeutral Neutral TNA

This article will only discuss COM classes with a ThreadingModel of Apartment, Free, or Both. When a COM object with a ThreadingModel registry setting of Apartment is created, it will be placed into an STA. When a COM object with a ThreadingModel registry setting of Free is created, it will be placed into an MTA. When a COM object with a ThreadingModel registry setting of Both is created, it will be placed into whatever apartment its creator is in. If the object creating it is in an STA, it will be placed into that same STA. If its creator is in an MTA, it will be placed into that same MTA. If the creator is in another process or on another machine, the object will be placed into an MTA.

Visual Basic 6.0 can only create COM classes with a ThreadingModel of Apartment. This means that all Visual Basic 6.0 objects will be placed into an STA. Visual C++ 6.0 allows you to create classes with a ThreadingModel of Apartment, Free, or Both.

The Change
Each server application (as opposed to library applications) in MTS has an STA thread pool of 100 threads. When MTS creates an object on behalf of a client, it places the object into one of the 100 STAs it has at its disposal. Once the object is created, it cannot be moved into another STA or executed by any thread other than the thread that is associated with that STA. When all 100 STAs have an object in them, MTS starts placing newly created objects into the existing STAs. This process is called multiplexing. If two objects in the same STA want to execute at the same time, one of them will have to wait for the other object to finish executing before it can execute. The performance of your middle-tier can start to suffer when MTS starts multiplexing COM objects.

In MTS all COM objects run in an STA, even COM objects that are marked Free or Both in the registry. Therefore all classes, regardless of their ThreadingModel registry setting, perform about the same.

In COM+ all that changed. COM+ has two thread pools, an STA thread pool and an MTA thread pool. The MTA thread pool has no limit to the number of threads that can be created and used by COM+. The STA, however, is very limited. The following excerpt from Microsoft Knowledge Base Article 282490 (INFO: Thread Pool Differences Between COM+ and MTS) describes how the COM+ STA thread pool works:

  • The thread pool size is initialized to 7 plus the number of processors.

  • The thread pool size increases with the number of queued requests.

  • The maximum size that the thread pool can reach is 10 multiplied by the number of processors.

  • The thread pool decreases in size when the traffic is low.
For a typical dual processor COM+ server, the STA thread pool is initialized to 9 threads and increases in size to 20 threads. This situation is much worse for a single processor COM+ server. The STA thread pool for COM+ applications running on a single processor COM+ server can only increase in size to 10 threads.

If a middle-tier application written in Visual Basic 6.0 running in NT 4.0/MTS is upgraded to Windows 2000/COM+, the application goes from having 100 threads of execution down to only 10 threads (or 10 times the number of processors on the server). This can cause a huge impact on performance.

One of Microsoft’s recommendations on how to restore performance when upgrading to Windows 2000/COM+ is to write all COM classes in a way that allows them to run in an MTA. This is not too hard to do in Visual C++, but it is impossible to do in Visual Basic 6.0. Ironically enough, Microsoft admits in an article written by Michael McKeown titled Preserving Application Performance When Porting from MTS to COM+ that under MTS, classes with a ThreadingModel of Apartment was the way to go. In COM+, however, a ThreadingModel of Both (or Free) is preferred.

In my opinion Microsoft really hurt Visual Basic 6.0 when they made the decision to restrict the number of threads in the STA thread pool in COM+. It would have been good if Microsoft had modified COM+ to allow a user to specify the number of threads in the STA thread pool or had released a Service Pack to Visual Basic 6.0 allowing COM classes created with Visual Basic 6.0 to have a ThreadingModel of Both or Free in addition to Apartment.

Example
The following are the results of an application I wrote to demonstrate how many threads COM+ was using for a given COM+ Server Application and how it assigned objects to those threads. Contact me if you would like a copy of the source code for the dlls and executable I used to generate this data. I ran all of my testing using a dual processor COM+ server.

The first test demonstrates how COM+ distributes Visual Basic 6.0 objects to threads in the STA thread pool. I created 140 instances of the CsgVbInternals.ThreadInfo class in a COM+ Server Application and recorded which thread the object was created on. The Instance column indicates which instance of the object was created; the Thread ID column indicates the ID of the windows thread the object was assigned to; and the Thread Count column is my application’s ID number for that thread.

Instance   Thread ID   Thread Count
118361
21522
314323
415844
518085
615246
78447
814968
913529
1013529
1114968
128447
1315246
1418085
1515844
1614323
171522
1818361
1918361
201522
2114323
2215844
2318085
2415246
258447
2614968
2713529
2813529
2914968
308447
3115246
3218085
3315844
3414323
351522
3618361
3718361
381522
3914323
4015844
4118085
4215246
438447
4414968
4513529
46159610
47159610
48159610
49159610
50159610
51152011
52152011
53152011
54152011
55152011
56139212
  
95170819
96138420
97138420
98138420
99138420
100138420
101138420
102170819
103151218
104147217
105165616
106126415
107160014
108167613
109139212
110152011
111159610
11213529
11314968
1148447
11515246
11618085
11715844
11814323
1191522
12018361
12118361
1221522
12314323
12415844
12518085
12615246
1278447
12814968
12913529
130159610
131152011
132139212
133167613
134160014
135126415
136165616
137147217
138151218
139170819
140138420

COM+ creates 9 threads for the STA thread pool and starts to allocate the newly created COM objects to them. Once each thread has 5 objects associated with it, it starts to create additional threads. Each new thread gets 5 objects assigned to it. Once all threads have 5 objects associated with them, a new thread is created. This continues until 20 threads are created, each having 5 objects assigned to them. Then COM+ starts assigning each newly created object to one of the existing 20 thread. This continues until all threads have 6 objects. Once all threads have 6 objects, COM+ starts assigning a seventh object to each thread. This will continue indefinitely.

If you have an enterprise software solution that will have over 9 (in the case of a dual processor machine) COM objects created on a single COM+ server, you are guaranteed to have multiple objects on the same STA thread.

The results are the same if you create a COM class with a ThreadingModel of Apartment in Visual C++. If, however, you create a COM class with a ThreadingModel of Both or Free, you get a much different result. The following are the results of running a testing that created 140 instances of the CsgComPlusTestLib.FreeInfo class.

Instance   Thread ID   Thread Count
117161
217161
317161
417161
  
13817161
13917161
14017161

COM+ executed all 140 instances of the object on the same thread. Since objects with a ThreadingModel of Free can be executed on any thread in the MTA thread pool, COM+ only needs to create a new thread if all existing threads are busy. Since I only had one application creating the objects, there was never any reason for COM+ to create another thread.

The following are some of the results generated by running three instances of my test application that created CsgComPlusTestLib.FreeInfo objects.

Instance   Thread ID   Thread Count
117161
215762
315762
417161
517161
  
5817161
5915762
6017161
6117161
6217283
6317283
  

Since there are up to three objects executing at the same time, you see three different threads in the results.

Since objects in an MTA can be executed on any thread in that MTA’s thread pool, COM+ can make much better use of existing threads.

How to Restore Performance
Microsoft’s primary recommendation on how to solve this problem is to make all COM+ classes have a ThreadingModel of Free or Both. This means Visual Basic 6.0 cannot be used to create classes that will be used in COM+. If the middle-tier for a large, enterprise software solution was written in Visual Basic 6.0, odds are it is not going to be rewritten in another language that supports a ThreadingModel of Free or Both anytime soon.

As of Post Windows 2000 Service Pack 2 COM+ Rollup Hotfix 10 (see Knowledge Base Article 294510) Microsoft added a registry setting (HKEY_LOCAL_MACHINE\Software\Microsoft\COM3\STAThreadPool Value name: EmulateMTSBehavior Data type: REG_DWORD) that allows COM+ to use the threading model MTS used. This allows COM+ to create 100 threads in its STA thread pool. Knowledge Base Article 303071 (Registry key for tuning COM+ thread and activity) discusses how to use this registry setting.

The following are the results of running my test application creating the CsgVbInternals.ThreadInfo class on a COM+ server that has had this registry setting changed.

Instance   Thread ID   Thread Count
119041
219442
317523
  
97224897
98225298
99225699
1002260100
1012260100
102225699
103225298
104224897
  
19817523
19919442
20019041
20119041

This registry setting allows Visual Basic 6.0 COM objects to perform the same way in COM+ as they did in MTS.

References
Transactional COM+: Building Scalable Applications
by Tim Ewald
http://www.amazon.com/exec/obidos/
tg/detail/-/0201615940/qid%3D1131988786


Preserving Application Performance When Porting from MTS to COM+
by Michael McKeown
http://msdn.microsoft.com/library/en-us/dncomser/
html/portingwinntapps_mtstocom.asp
(this link is broken, but the article can be found at http://web.archive.org/web/20060911204243/http://msdn.microsoft.com/library/en-us/dncomser/html/portingwinntapps_mtstocom.asp)

INFO: Thread Pool Differences Between COM+ and MTS
Knowledge Base Article: 282490
https://support.microsoft.com/en-us/kb/282490

Registry key for tuning COM+ thread and activity
Knowledge Base Article: 303071
https://support.microsoft.com/en-us/kb/303071

INFO: Post Windows 2000 Service Pack 2 COM+ Rollup Hotfix 10 Is Available
Knowledge Base Article: 294510
http://support.microsoft.com/kb/294510/

1 comment:

David B said...

Hi,

We have stumbled across your blog post whilst investigating a performance issue we have with one of our applications which is written in VB6 / COM+.

I know it's a while, but I don't suppose you still have the code you used for tests? We are trying to recreate the issues we found at our customers site.

Many thanks

David Betteridge

David.Betteridge AT proactis.com