have received a lot of feedback from my first two articles about common programming traps in MTS, which were published at VB-2-The-Max in December 1999. Perhaps there are a million experienced MTS programmers after all? 😉
What makes me even happier is that the feedback wasn’t only numerous, it was very positive and interesting too. Because of that I just have to share some of the discussions with you, especially some comments from Troy Cambra at Microsoft, but first I will increase the performance gain of “Problem 5: Not using SPM”, following a discussion with Charles C. Poston. (Thanks Charles!)
CreateInstance and SPM
Charles asked me if
CreateInstance is the correct way to instantiate the SPM. In my test code I used
CreateInstance without thinking very much about it, but when I used
New instead the improvement was raised from 130% to more than 1000%! But once again, remember what test environment I used for the tests. (See “18 Common Programming Traps in MTS, Part 1”.)
And while I’m discussing different instantiation techniques I think I should mention something else on this subject. Ed Pinto points this out as an item that frequently attracts questions in the different MTS/COM+ newsgroups. I’m sure most of you, like Ed, are tired of this discussion, but lets go over it quickly once more. Hopefully I wont make the topic more confusing than before.
This is the rule of thumb that I use: When the client needs to create an instance of an MTS component, use
CreateObject. When an MTS component needs to create another instance of an MTS component, use
CreateInstance. (If you are using COM+ instead of MTS, skip
CreateInstance and use
CreateObject here too.
CreateInstance will work, but only for backward compatibility). When creating non-MTS components from an MTS component (such as when you are creating an ADO recordset for example or the SPM for that matter), use
New. (If you prefer a little more complicated rule of thumb, then you can change
New in all cases above, except when the calling component lives in the same VB project as the called component.) (Thanks Ed!)
OK, lets move on to the comments from Troy.
Recommendation Given for Problem 5: Not Using SPM
“This is potentially a very bad recommendation. The Shared Property Manager does not scale well in many circumstances. On a single processor box it is fine and in the single client test, it is great, but as you add processors and users, you will often find the scaling/performance curve to be opposite to what you expect. Under any load (concurrent users), you will typically see SPM scale inversely with the number of processors. This is because the implementation is rather simplistic and serializes all reads as well as writes. We are going to be coming out with several new best-practices documents and most of them recommend against SPM for such a purpose. SPM is OK in situations were contention will not be an issue or where scalability is not an issue, but other than that, it is usually quite detrimental.
Storing state in the database is currently the best practice and will continue to be so in the immediate future. Writing a solid, well-performing, well-scaling general purpose in-memory cache is extremely difficult. Databases have had decades to optimize and tune accordingly and as such are simply much better at managing state.”
I have discussed this further with Troy and I now realize that my recommendation isn’t as important as I thought it was. If the number of concurrent users is low, then the SPM is efficient. Otherwise you have to take care with the usage of SPM. (As always, testing your application in a situation that is as realistic as possible is the best recommendation!)
I guess that this could be the reason for some recommendations from Microsoft that the SPM is not the best place for storing the connection string. The connection string is a typical example of a value needed all the time by several of the components and therefore the contention rate will be high.
Recommendation Given for Problem 12: Too Many COM Exceptions
“This is fine as long as you do not need to pass back extended error information. As soon as you have the need to do that, this approach falls apart. Sure, you could return an error structure at every call, but that imposes a non-error condition penalty because you are passing it all of the time. Further, once you start doing this, you will probably find that the error-condition expense is on par with the COM implementation.
The COM implementation does not result in extra round trips–if the default error objects are used. COM simply checks if an error has been set and simply marshals the object back with the returning call. The COM standard specifies that ErrorInfo objects marshal themselves by value and the default objects do just that. However, some have chosen to implement error objects that fail to marshal themselves by value in some cases. These are bugs in those products/technologies and should be reported as such.
Also, the COM error object provides a standard mechanism for propagating and handling errors. This may or may not be a design consideration, but if you are developing components for consumption by others, sticking with a standard mechanism is always nice.
That said, I have to agree with you that using the Err.Raise mechanism for raising errors is expensive. This is not just a VB or a COM issue. C++ and Java try/catch/throw syntax is also expensive. Within a given component using methodologies such as what you describe is a good idea in many cases, but really consider what you expose outside of your component. It may or may not be OK to do what you recommend, but I would not make a blanket recommendation.”
I understand that I made an error in my article about the number of roundtrips. (Unfortunately I didn’t check that myself. I think that I read it somewhere. You shouldn’t believe everything you read, right?)
That said, my intention with recommendation 12 was not to skip
Err.Raise when you need to raise an error. In my opinion that is the correct way to do it. What I meant was that over-use is never good and that applies here too. I have seen recommendations that you should almost skip functions, and use subs with
Err.Raise instead. In my opinion, you shouldn’t use
Err.Raise for “natural” return values. That way you will pay a performance penalty. (And end up with ugly code.)
I definitely agree with Troy that components that are to be used “publicly” should use COM Exceptions instead of C-style return values for reporting errors.
In Sweden we have one small untranslatable word called “lagom”. It means not too much and not too little. Lets call it “enough”. Enough is best! End of lecture in foreign languages for today. 😉
Problem #19: Use Retain in Memory
“All VB components that run in MTS or IIS must have the “Unattended Execution” and “Retain in Memory” settings checked. We (Microsoft) have been very remiss in not making this more known. The “Retain in Memory” setting has been documented as merely a performance improvement setting when in fact, it only provides a minimal performance improvement. The real benefit is in reliability. You can not have reliability without it. Try this test:
- Create a simple do nothing MTS component without those settings (just return a string or something).
- Create a simple client to call the do nothing component repeatedly in a loop (Create/Use/Release).
- Install the component in MTS.
- Run the test
- Open Perfmon or Task Manager and watch the handle count in that MTX process.
It should be leaking handles like a sieve. This is just one symptom of the underlying problem, which is that the VB project and thread construction/destruction code is not threadsafe or stable under stress. This problem has been around forever. You might be familiar with the VB5 clsEmpty fix (Q186273). This is the same problem. The “Retain in Memory” setting in VB6 just does what the clsEmpty workaround did in VB5 – only a little neater. We have finally put out a KB on this issue, but it is still incomplete in its description of the problem and the symptoms. It is KB article Q241896.”
When I started writing the articles I had something like 25 ideas that I tried out. “Retain in Memory” was one of them, but I couldn’t prove more than a very small performance improvement with it so I skipped that recommendation. Now I understand its importance and I will never forget to check that setting. I don’t think I have forgotten it in the past, but its always nice to know why you should do something.
I think I was eager in the first article to point out the problem with performance metrics. You can see one of the pitfalls above, where it turns out that some optimization tips will actually reduce performance in some configurations and with a more realistic load. The lesson to learn is that you have to check for yourself using your own configuration, with your own application and with as realistic usage as possible. I guess this is obvious and not new for you, but I will repeat it anyway.
Once again, thanks for all your kind feedback and a special thanks to Troy Cambra at Microsoft! I got a lot of other ideas too thanks to the feedback, but I will save them for future articles.