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 Value | ThreadingModel Registry Setting | Apartment |
COMAdminThreadingModelApartment | Apartment | STA |
COMAdminThreadingModelFree | Free | MTA |
COMAdminThreadingModelMain | {none} | Main STA |
COMAdminThreadingModelBoth | Both | 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.
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 1 1836 1 2 152 2 3 1432 3 4 1584 4 5 1808 5 6 1524 6 7 844 7 8 1496 8 9 1352 9 10 1352 9 11 1496 8 12 844 7 13 1524 6 14 1808 5 15 1584 4 16 1432 3 17 152 2 18 1836 1 19 1836 1 20 152 2 21 1432 3 22 1584 4 23 1808 5 24 1524 6 25 844 7 26 1496 8 27 1352 9 28 1352 9 29 1496 8 30 844 7 31 1524 6 32 1808 5 33 1584 4 34 1432 3 35 152 2 36 1836 1 37 1836 1 38 152 2 39 1432 3 40 1584 4 41 1808 5 42 1524 6 43 844 7 44 1496 8 45 1352 9 46 1596 10 47 1596 10 48 1596 10 49 1596 10 50 1596 10 51 1520 11 52 1520 11 53 1520 11 54 1520 11 55 1520 11 56 1392 12 …
95 1708 19 96 1384 20 97 1384 20 98 1384 20 99 1384 20 100 1384 20 101 1384 20 102 1708 19 103 1512 18 104 1472 17 105 1656 16 106 1264 15 107 1600 14 108 1676 13 109 1392 12 110 1520 11 111 1596 10 112 1352 9 113 1496 8 114 844 7 115 1524 6 116 1808 5 117 1584 4 118 1432 3 119 152 2 120 1836 1 121 1836 1 122 152 2 123 1432 3 124 1584 4 125 1808 5 126 1524 6 127 844 7 128 1496 8 129 1352 9 130 1596 10 131 1520 11 132 1392 12 133 1676 13 134 1600 14 135 1264 15 136 1656 16 137 1472 17 138 1512 18 139 1708 19 140 1384 20
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 1 1716 1 2 1716 1 3 1716 1 4 1716 1 … 138 1716 1 139 1716 1 140 1716 1
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 1 1716 1 2 1576 2 3 1576 2 4 1716 1 5 1716 1 … 58 1716 1 59 1576 2 60 1716 1 61 1716 1 62 1728 3 63 1728 3 …
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 1 1904 1 2 1944 2 3 1752 3 … 97 2248 97 98 2252 98 99 2256 99 100 2260 100 101 2260 100 102 2256 99 103 2252 98 104 2248 97 … 198 1752 3 199 1944 2 200 1904 1 201 1904 1
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
html/portingwinntapps_mtstocom.asp
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://web.archive.org/web/20120510152811/http://support.microsoft.com/kb/282490
Registry key for tuning COM+ thread and activity
Knowledge Base Article: 303071
https://static.heironimus.info/blog/msdn/303071.html
INFO: Post Windows 2000 Service Pack 2 COM+ Rollup Hotfix 10 Is Available
Knowledge Base Article: 294510
https://static.heironimus.info/blog/msdn/294510.html
1 comment:
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
Post a Comment