Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

C/C++

Method Call Interception


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 the MethodInfo. You can't keep Mci a static object in the method because then the constructor is called only once. You can't make the m_methodInfo data member of Mci a static object in the constructor because it is shared by all Mci 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 the Mci 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 calls MethodAnalyzer::Analyze() and stores the resulting MethodInfo 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 #ifdefs 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 tricks—this 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 simple—the 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 aspects—entering 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++.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.