Ad Hoc MCI
The InjectMci.py script is a lifesaver when you need it, but requires integration with the build process or manually running it every time you add a new method. Also, logging each and every method in your code base might be a little excessive. Sometimes, all you want is a quick benchmark or to sift through the call graph of a few selected functions. In this case, adding a couple of Mci
objects manually is completely reasonable. However, doing it for more than three or four methods gets old really fast. You have to paste the Mci
line, then type the method definition as the third string argument (or copy-and-paste it and lose the Mci
line in your clipboard). The solution is running InjectMci.py in selective mode. Instead of putting an Mci
object in every method, it will put it only in methods that contain INJECT_MCI
. This way, you can quickly annotate a few methods with INJECT_MCI
and be done with it. To use InjectMci.py in selective mode, just pass selective
as a command-line argument.
MCI Overhead
MCI puts a heavy load on your program if used precariously. The work of parsing the method definition line might slow your program to a crawl in tight loops, which call noninline methods (bad idea in general). Remember that MCI is primarily a development aid and should not be active even at development time while stress testing your code. There are several things you can do to reduce the performance hit incurred by MCI:
- Do not put MCI in inline functions. Inline functions are often called in tight loops, and generally should execute as fast as possible. The easiest thing to do is to put MCI objects only in functions defined in .cpp files, which may or may not work in all cases. For example, if you always define inline functions in .H files, it works perfectly, but if you define inline functions in .cpp files and then
#include
the .cpp file, it won't work; see Example 4. - Employ smart filtering during the MCI automation, so that infrastructure classes such as custom containers or communication code are not burdened by MCI.
- If you don't care about dynamic method interception, cache the resulting
MethodInfo
struct. The question is where to keep theMethodInfo
. You can't keepMci
a static object in the method because then the constructor is called only once. You can't make them_methodInfo
data member ofMci
a static object in the constructor because it is shared by allMci
instances. The only solution I could think of is to keep a static map indexed by a pairing of a filename and line (which is guaranteed to be unique). Whenever theMci
constructor is called, it first queries the cache using the filename and line parameter. If this method's info is already cached, then it just uses it to notify the sink; otherwise, it callsMethodAnalyzer::Analyze()
and stores the resultingMethodInfo
in the cache. However, searching a huge cache can hurt even more (think about CPU cache misses). I didn't implement such a system. - Use preprocessor
#define
to enable/disable MCI. This method puts the responsibility to enable/disable MCI in each method or file separately. It is simpler than a full-scale filtering system, but doesn't scale well and requires high maintenance. In addition, it smudges ugly preprocessor#ifdef
s all over the code; see Example 5.
Example 4: (a) GoodInline.h; (b) BadInline.h; (c) BadInline.cpp.
(a)
// Good : no MCI for .h file void inline Foo() { ... }
(b)
void inline Foo(); #include Inline.cpp
(c)
void inline Foo() { // Bad : Mci injected for .cpp Mci mci(...); ... }
Example 5: Overhead.
void inline Foo() { #ifdef ENABLE_MCI Mci mci(...); #endif ... }
Packaging
MCI is packaged as a static library that you link with your target project. I used pure Standard C++. I tested it using Visual C++ 6 and Visual C++.NET 2003. It should compile under any C++ compiler (no templates or other tricksthis time).
Poor Man's Profiler
PMP is a Windows console application that uses MCI for simple method-profiling tasks. The profiler (available at http://www.cuj .com/code/) implements the IMciEvents
interface, measures how long each method of the A
and B
classes takes, and writes the results to standard output. Beware that this is a simple implementation that breaks under nested calls (no stack for start times), not to mention multithreaded scenarios. The motivation is to show what an Mci
sink looks like and how it interacts with MCI. You should not try to sell it to your boss as the next generation of profiling technology. Listing Four contains the Profiler
class that derives from IMciEvents
and implements
its two methods: OnEnter()
and OnLeave()
. The implementation is just as simplethe start time of the method is stored in the OnEnter
event and the duration is calculated by subtracting the current tick count from the stored start time.
Listing Four
(a)
#ifndef __PROFILER_H__ #define __PROFILER_H__ #include <windows.h> #include "IMciEvents.h" class Profiler : public IMciEvents { public: // IMciEvents methods virtual void OnEnter(const std::string & filename, int lineNumber, const MethodInfo & mi); virtual void OnLeave(const std::string & filename, int lineNumber, const MethodInfo & mi); private: DWORD m_start; }; #endif
(b)
#include "Profiler.h" #include "MethodInfo.h" #include <iostream> using std::cout; using std::endl; using std::string; void Profiler::OnEnter(const string & filename, int lineNumber, const MethodInfo & mi) { m_start = ::GetTickCount(); } void Profiler::OnLeave(const string & filename, int lineNumber, const MethodInfo & mi) { DWORD duration = ::GetTickCount() - m_start; cout << endl << "Method '" << mi.className << "::" << mi.name << "' took " << duration << " milli-seconds"; }
This is the right point to yell: "Hey, the profiler didn't register itself as the Mci
sink." This is true and intentional. Even in such a simple setup, the event source (Mci
) and the event handler (Profiler
) don't know each other (don't #include
their respective .h files). The registration is done by the main
function in this case (see Example 6). This is an idiom of component-based development called "Third Party Binding." Mci
gets a pointer to the Profiler
, but actually it sees an IMciEvents
pointer thanks to the automatic upcasting. The Profiler
class is totally oblivious to the existence of Mci
. All it knows is that someone is supposed to call its OnEnter()
and OnLeave()
method. Period. The same goes for Mci
. It knows nothing about the Profiler
. All it knows is that in its constructor, it should call the registered sink's OnEnter()
method and in its destructor it should call the sink's OnLeave()
method. Period. This is another idiom called "Abstract Interactions."
Example 6: Registration.
int main(int argc, char* argv[]) { Profiler p; Mci::Register(&p); ... }
Note on #defining {
When I first conjured the idea of MCI, I thought about making it look completely transparent. I wanted to #define
the opening curly brace like this:
#define { { Mci m(...);
The code would have looked totally normal (sans Mci
), but under the covers, MCI would have done its stuff. There are many problems with this approach, such as the fact that every opening curly brace gets an MCI object including regular scope braces (if
, while
, for
, and so on), and braces in namespace, enum, struct, and class definitions:
namespace { Mci m(...); enum { Mci m(...); SUNDAY, MONDAY... }
Yet, what really threw me off this track is the simple fact that you can't #define
curly braces. If I think objectively about it, it looks like the only advantage for this approach is the "Wows" I would have gotten from my colleagues (which clearly makes it a worthwhile endeavor).
Conclusion
MCI combines several C++ features, techniques, and idioms creatively to provide an easy-to-use solution for a limited domain of cross-cutting aspectsentering and leaving method calls during development time. However, using MCI and MCI automation (through Python) is not a no-brainer (in other words, it's a brainer) and you must consider the significant overhead MCI puts on your code and adapt it to your needs.
Gigi Sayfan is a software developer specializing in object-oriented and component-oriented programming using C++.