|
|
/*********************************************************************************
/* File:
/* PROFILE.H
/* Author:
/* Max-H. Windisch, SDE-T
/* Date:
/* October 1996
/* Macros:
/* BEGIN_PROFILING_BLOCK
/* END_PROFILING_BLOCK
/* DUMP_PROFILING_RESULTS
/* IMPLEMENT_PROFILING
/* IMPLEMENT_PROFILING_CONDITIONAL
/* Classes:
/* CMaxLargeInteger
/* CMaxTimerAbstraction
/* CMaxMiniProfiler_Node_Base
/* CMaxMiniProfiler_Node_Standard
/* CMaxMiniProfiler_Node_NoHistory
/* CMaxMiniProfiler_Base
/* CMaxMiniProfiler_Standard
/* CMaxMiniProfiler_NoHistory
/* CMaxMultithreadProfiler
/* CMaxProfilingDLLWrapper
/* CMaxProfilingObject
/* CMaxProfilingBlockWrapper
/* Summary:
/* This mini profiler allows you to place BEGIN_PROFILING_BLOCK and
/* END_PROFILING_BLOCK directives in your code, or use the
/* CMaxProfilingBlockWrapper object, and collect results
/* in a logfile on termination of the profiled application (or by
/* using the DUMP_PROFILING_RESULTS macro). The
/* profiling blocks can be nested. Each module (DLL/EXE) using
/* the profiler must use IMPLEMENT_PROFILING or
/* IMPLEMENT_PROFILING_CONDITIONAL exactly once (defines
/* static variables for the profiler)
/* More details:
/* The default result file is c:/result.txt. It is not erased
/* automatically. For each completed instance of a profiler, it
/* contains: 1) a header, 2) the history of all profiled blocks (optional),
/* 3) merged results. For merging, results are sorted by {level, name},
/* merged, then sorted again by {full name}. Therefore, block names
/* must be unique. In any case, absolute results are always
/* given (in seconds)
/* How to enable in your code:
/* To enable the profiler, define MAX_PROFILING_ENABLED before including
/* this file. To use the profiler through s:/ds/util/maxprof.dll
/* (built in release), define MAX_PROFILING_ENABLED_DLL instead. This
/* allows to use one single instance of a profiler from multiple
/* modules
/* Other comments:
/* At runtime, you can disable history output by defining the following
/* environment variable to YES: MAX_DISABLE_PROFILING_HISTORY.
/* In DLL mode, if you define MAX_PROFILING_CONDITIONAL before including
/* this file, the profiler will work only if the following environment
/* variable is defined to YES: MAX_ENABLE_PROFILING
/* Note:
/* It's on purpose that I avoid using virtual methods here
/*
/* (c) Copyright 1996 Microsoft-Softimage Inc.
/********************************************************************************/ #ifndef __MAX_PROFILING_H // {
#define __MAX_PROFILING_H
#include <afx.h> // for CTime and CString
#include <assert.h> // for asserts
#include <fstream.h> // for streams
#include <iomanip.h>
//#pragma warning( disable : 4786 ) // stl antivirus ;-)
//#include <dsstlmfc.h> // for STL
#define MAX_ENV_ENABLE_PROFILING _T( "MAX_ENABLE_PROFILING" )
#define MAX_ENV_DISABLE_PROFILING_HISTORY _T( "MAX_DISABLE_PROFILING_HISTORY" )
#define MAX_ENV_YES _T( "YES" )
#define MAX_ENV_ALL _T( "ALL" )
#if !defined( DS_ON_AXP ) && !defined( _NO_THROW )
#define MAXPROFNOTHROW __declspec( nothrow )
#else
#define MAXPROFNOTHROW
#endif
#define MAX_PROFTAGNODE_TOP "PROFILER: ALL"
#define MAX_PROFTAGNODE_HEAPALLOCATION "PROFILER: HEAPALLOCATION"
#define MAX_PROFTAGNODE_BIAS "PROFILER: BIAS"
#define MAX_PROFTAGNODE_NOTHINGNESS "PROFILER: NOTHINGNESS"
// Note: disable profiling in _SHIP (unless specified otherwise by DS_PROFILE_SHIP),
// and in unix (not sure why)
#if ( defined _SHIP && !defined DS_PROFILE_SHIP ) || defined unix
#undef MAX_PROFILING_ENABLED_DLL
#undef MAX_PROFILING_ENABLED
#endif
/*********************************************************************************
/* Macros:
/* BEGIN_PROFILING_BLOCK
/* END_PROFILING_BLOCK
/* DUMP_PROFILING_RESULTS
/* IMPLEMENT_PROFILING
/* IMPLEMENT_PROFILING_CONDITIONAL
/* Comments:
/* . For simplified use of CMaxMiniProfiler's.
/* . For the comment parameter, use a non-unicode string, without "return"
/* character
/* . For the enabler parameter, use a unicode string (the name of your
/* environment variable)
/* . Use unique comments, since the profiler might use them as sorting keys
/* . The use of DUMP_PROFILING_RESULTS is not compulsory, since profilings
/* are always dumped at the end of a profiling session
/********************************************************************************/ #ifndef unix
#define __MAX_RESULTFILE_NAME "c:\\result.txt"
#else
#define __MAX_RESULTFILE_NAME "result.txt"
#endif
#ifdef MAX_PROFILING_ENABLED_DLL
#define __MAX_MINIPROFILER_IMPLEMENTATION ;
#else
#define __MAX_MINIPROFILER_IMPLEMENTATION \
const char *CMaxMiniProfiler_Base::s_poDefaultFileName = __MAX_RESULTFILE_NAME; \ CMaxTimerAbstraction CMaxMiniProfiler_Base::s_oOutOfBraceBiasApproximation; \ CMaxTimerAbstraction CMaxMiniProfiler_Base::s_oInOfBraceBiasApproximation; \ bool CMaxMiniProfiler_Base::s_bBiasIsKnown = false; \ unsigned long CMaxMiniProfiler_Base::s_lHeapBlockSize = 5000; #endif
#if defined MAX_PROFILING_ENABLED || defined MAX_PROFILING_ENABLED_DLL // {{
#define BEGIN_PROFILING_BLOCK( comment ) \
CMaxProfilingObject::SCreateNewNode( comment ); #define END_PROFILING_BLOCK \
CMaxProfilingObject::SCloseCurrentNode(); #define DUMP_PROFILING_RESULTS \
CMaxProfilingObject::SDumpResults(); #define IMPLEMENT_PROFILING \
__MAX_MINIPROFILER_IMPLEMENTATION \ CMaxProfilingObject::MPOProfiler CMaxProfilingObject::s_oProfiler; \ CMaxProfilingObject::__CBiasApproximation CMaxProfilingObject::s_oBiasApproximation; #define IMPLEMENT_PROFILING_CONDITIONAL( enabler ) \
__MAX_MINIPROFILER_IMPLEMENTATION \ CMaxProfilingObject::MPOProfiler CMaxProfilingObject::s_oProfiler( enabler ); \ CMaxProfilingObject::__CBiasApproximation CMaxProfilingObject::s_oBiasApproximation; #else // }{
#define BEGIN_PROFILING_BLOCK( comment ) ( void )( comment );
#define END_PROFILING_BLOCK ;
#define DUMP_PROFILING_RESULTS ;
#define IMPLEMENT_PROFILING ;
#define IMPLEMENT_PROFILING_CONDITIONAL( enabler ) ;
#endif // }}
#if defined MAX_PROFILING_ENABLED || defined MAX_PROFILING_ENABLED_DLL || defined MAX_PROFILING_DLL_IMPLEMENTATION // {
/*********************************************************************************
/* Helper function:
/* bGIsEnabledEnvVar
/* Comments:
/********************************************************************************/ MAXPROFNOTHROW static inline bool bGIsEnabledEnvVar( const TCHAR *pszEnvironmentVariableName, const TCHAR *pszCriteria = MAX_ENV_YES ) { const int nLength = 80; TCHAR szBuffer[ nLength ]; DWORD dwValue;
// NULL string means enabled (default)
if ( NULL == pszEnvironmentVariableName ) return true;
dwValue = ::GetEnvironmentVariable( pszEnvironmentVariableName, szBuffer, nLength ); if ( dwValue > 0 && _tcsicmp( szBuffer, pszCriteria ) == 0 ) return true;
return false; }; #endif // }
#if defined MAX_PROFILING_ENABLED || defined MAX_PROFILING_DLL_IMPLEMENTATION // {
/*********************************************************************************
/* Class:
/* CMaxLargeInteger
/* Comments:
/* Minimal encapsulation of LARGE_INTEGER, considered as a time value
/********************************************************************************/ class CMaxLargeInteger { protected: LARGE_INTEGER m_oValue;
public: MAXPROFNOTHROW CMaxLargeInteger( LONG lHighPart = 0, DWORD dwLowPart = 0 ) { m_oValue.u.HighPart = lHighPart; m_oValue.u.LowPart = dwLowPart; }
MAXPROFNOTHROW CMaxLargeInteger( LONGLONG llQuadPart ) { m_oValue.QuadPart = llQuadPart; }
MAXPROFNOTHROW CMaxLargeInteger operator +( const CMaxLargeInteger &roAdded ) const { return CMaxLargeInteger( m_oValue.QuadPart + roAdded.m_oValue.QuadPart ); }
MAXPROFNOTHROW CMaxLargeInteger operator -( const CMaxLargeInteger &roSubstracted ) const { return CMaxLargeInteger( m_oValue.QuadPart - roSubstracted.m_oValue.QuadPart ); }
MAXPROFNOTHROW CMaxLargeInteger operator /( unsigned long lDivisor ) const { return CMaxLargeInteger( m_oValue.QuadPart / ( LONGLONG )lDivisor ); }
MAXPROFNOTHROW bool operator <( const CMaxLargeInteger &roCompared ) const { return m_oValue.QuadPart < roCompared.m_oValue.QuadPart; }
MAXPROFNOTHROW operator LARGE_INTEGER*() { return &m_oValue; }
MAXPROFNOTHROW LONG lFGetHighPart() const { return m_oValue.u.HighPart; }
MAXPROFNOTHROW DWORD dwFGetLowPart() const { return m_oValue.u.LowPart; }
MAXPROFNOTHROW double dFInSecondsF( const CMaxLargeInteger &roFreq ) const { const DWORD dwMaxDword = 0xffffffff; double highunit; assert( 0 == roFreq.m_oValue.u.HighPart && 0 != roFreq.m_oValue.u.LowPart );
highunit = ( ( double )dwMaxDword + 1.0 ) / ( double )roFreq.m_oValue.u.LowPart; return ( ( ( double )m_oValue.u.HighPart * highunit ) + ( ( double )m_oValue.u.LowPart / roFreq.m_oValue.u.LowPart ) ); } };
MAXPROFNOTHROW inline ostream& operator<<( ostream &os, const CMaxLargeInteger &val ) { return os << "(" << ( unsigned long )val.lFGetHighPart() << ";" << ( unsigned long )val.dwFGetLowPart() << ")"; };
/*********************************************************************************
/* Class:
/* CMaxTimerAbstraction
/* Comments:
/* Defines the interface CMaxMiniProfiler's expect from any timer
/* implementation
/********************************************************************************/ class CMaxTimerAbstraction { protected: CMaxLargeInteger m_oTime; static const CMaxLargeInteger s_oFrequency;
public: MAXPROFNOTHROW CMaxTimerAbstraction(){ /* assumed to zero its internal value */ } MAXPROFNOTHROW CMaxTimerAbstraction( int ){ ::QueryPerformanceCounter( m_oTime ); } MAXPROFNOTHROW CMaxTimerAbstraction( const CMaxTimerAbstraction &roSrc ) : m_oTime( roSrc.m_oTime ){} MAXPROFNOTHROW const CMaxTimerAbstraction& operator =( const CMaxTimerAbstraction &roSrc ){ m_oTime = roSrc.m_oTime; return *this; }
protected: // Note: not part of the interface; for internal use only
MAXPROFNOTHROW CMaxTimerAbstraction( const CMaxLargeInteger &roSrc ) : m_oTime( roSrc ){};
public: MAXPROFNOTHROW void FLog() { ::QueryPerformanceCounter( m_oTime ); }
MAXPROFNOTHROW double dFInSeconds() const { return m_oTime.dFInSecondsF( s_oFrequency ); }
public: MAXPROFNOTHROW void FAdd( const CMaxTimerAbstraction &roAdded ) { m_oTime = m_oTime + roAdded.m_oTime; }
MAXPROFNOTHROW void FSubstract( const CMaxTimerAbstraction &roSubstracted ) { #if 0
// special case for negative differences - hide them
if ( m_oTime < roSubstracted.m_oTime ) { m_oTime = CMaxLargeInteger( 0, 1 ); return; } #endif
m_oTime = m_oTime - roSubstracted.m_oTime; }
MAXPROFNOTHROW void FDivide( unsigned long lDivisor ) { m_oTime = m_oTime / lDivisor; }
public: MAXPROFNOTHROW static CMaxTimerAbstraction oSSum( const CMaxTimerAbstraction &roArg1, const CMaxTimerAbstraction &roArg2 ) { CMaxTimerAbstraction sum;
sum.m_oTime = roArg1.m_oTime + roArg2.m_oTime; return sum; }
MAXPROFNOTHROW static CMaxTimerAbstraction oSDifference( const CMaxTimerAbstraction &roArg1, const CMaxTimerAbstraction &roArg2 ) { CMaxTimerAbstraction difference;
#if 0
// special case for negative differences - hide them
if ( roArg1.m_oTime < roArg2.m_oTime ) return CMaxTimerAbstraction( CMaxLargeInteger( 0, 1 ) ); #endif
difference.m_oTime = roArg1.m_oTime - roArg2.m_oTime; return difference; }
MAXPROFNOTHROW static bool bSLess( const CMaxTimerAbstraction &roArg1, const CMaxTimerAbstraction &roArg2 ) { return roArg1.m_oTime < roArg2.m_oTime; }
MAXPROFNOTHROW static CMaxTimerAbstraction oSFrequency() { return CMaxTimerAbstraction( s_oFrequency ); }
private: MAXPROFNOTHROW static CMaxLargeInteger oSCentralFrequency() { CMaxLargeInteger frequency;
::QueryPerformanceFrequency( frequency ); return frequency; }
friend ostream& operator<<( ostream &os, const CMaxTimerAbstraction &val ); };
MAXPROFNOTHROW inline ostream& operator<<( ostream &os, const CMaxTimerAbstraction &val ) { return os << val.m_oTime; };
/*********************************************************************************
/* Class:
/* CMaxMiniProfiler_Node_Base
/* Comments:
/* Basic profiling node that behaves like a chronometer, and provides
/* standard logging services. Both the Standard and NoHistory profilers
/* use this basic implementation
/********************************************************************************/ class CMaxMiniProfiler_Node_Base { public: typedef CString MMPNBString;
public:
// comparison by index
// -------------------
class CCompareIndexes { public: MAXPROFNOTHROW bool operator()( const CMaxMiniProfiler_Node_Base &o1, const CMaxMiniProfiler_Node_Base &o2 ) const { assert( &o1 != &o2 ); return ( o1.m_lIndex < o2.m_lIndex ); }; }; friend CCompareIndexes;
protected:
// acquired at initialization
// --------------------------
unsigned long m_lLevel; const char *m_pszTitle;
unsigned long m_lIndex; // internal time counting mechanism
// --------------------------------
CMaxTimerAbstraction m_taOrigin; CMaxTimerAbstraction m_taDelta; unsigned int m_nCount; #ifdef _DEBUG
bool m_bIsCounting; #endif
// for final output
// ----------------
double m_dDelta;
public:
// constructor etc.
// ----------------
// Note: uses default assignment and copy constructor
// Note: it doesn't cost anything to initialize lots of things here - this
// is not done within profiling braces
MAXPROFNOTHROW CMaxMiniProfiler_Node_Base() : m_lLevel( 0 ) , m_pszTitle( NULL ) , m_lIndex( 0 ) , m_nCount( 0 ) , m_dDelta( 0 ) #ifdef _DEBUG
, m_bIsCounting( false ) #endif
{ };
// chrono
// ------
MAXPROFNOTHROW void FStart() { #ifdef _DEBUG
assert( !m_bIsCounting ); m_taOrigin.FLog(); m_nCount++; m_bIsCounting = true; #else
m_taOrigin.FLog(); m_nCount++; #endif
};
MAXPROFNOTHROW void FStop() { CMaxTimerAbstraction destination( 1 );
#ifdef _DEBUG
assert( m_bIsCounting ); m_taDelta.FAdd( CMaxTimerAbstraction::oSDifference( destination, m_taOrigin ) ); m_bIsCounting = false; #else
m_taDelta.FAdd( CMaxTimerAbstraction::oSDifference( destination, m_taOrigin ) ); #endif
};
// access to members
// -----------------
MAXPROFNOTHROW unsigned long lFGetLevel() const { return m_lLevel; }; MAXPROFNOTHROW const char *pszFGetTitle() const { return m_pszTitle; }; MAXPROFNOTHROW unsigned long lFGetIndex() const { return m_lIndex; }; MAXPROFNOTHROW const CMaxTimerAbstraction &roFGetOrigin() const { return m_taOrigin; }; MAXPROFNOTHROW CMaxTimerAbstraction &roFGetDelta() { return m_taDelta; }; MAXPROFNOTHROW unsigned int nFGetCount() const { return m_nCount; };
MAXPROFNOTHROW double dFGetDelta() { if ( 0 == m_dDelta ) FComputeDelta(); return m_dDelta; };
// misc. services
// --------------
MAXPROFNOTHROW bool bFIsIn( const CMaxMiniProfiler_Node_Base &roNode ) const { // Note: times cannot be equal, so we don't need to worry about that
if ( CMaxTimerAbstraction::bSLess( m_taOrigin, roNode.m_taOrigin ) ) { CMaxTimerAbstraction d1 = m_taOrigin; CMaxTimerAbstraction d2 = roNode.m_taOrigin;
d1.FAdd( m_taDelta ); d2.FAdd( roNode.m_taDelta ); if ( CMaxTimerAbstraction::bSLess( d2, d1 ) ) return true; } return false; };
MAXPROFNOTHROW void FConditionalRemove( const CMaxMiniProfiler_Node_Base &roNode, const CMaxTimerAbstraction &roBias ) { if ( bFIsIn( roNode ) ) { CMaxTimerAbstraction d = roNode.m_taDelta; d.FAdd( roBias ); m_taDelta.FSubstract( d ); } };
// output to file
// --------------
void FOutput( ostream &os ) { // don't output dead (merged) nodes
if ( 0 == m_nCount ) return;
// output our index
os << setw( 10 ) << m_lIndex << ": ";
// indent
STab( os, m_lLevel );
// output our title
os << "@@Name="; if ( NULL != m_pszTitle ) os << m_pszTitle;
// output our block count
os << " @@Count=" << m_nCount;
// output our delta t
os << " @@Duration="; SStampDeltaInSeconds( os, dFGetDelta() ); };
void FStampAbsoluteRange( ostream &os ) const { SStampAbsoluteRange( os, m_taOrigin, m_taDelta ); };
protected:
// computations at output time (outside of profiling)
// --------------------------------------------------
MAXPROFNOTHROW void FComputeDelta() { m_dDelta = m_taDelta.dFInSeconds(); };
public:
// mini helpers for facilitated and standardized output of results
// ---------------------------------------------------------------
static ostream& STab( ostream &os, int level ) { for ( int i = 0; i < level; i++ ) os << " "; return os; };
static ostream& SStampDeltaInSeconds( ostream &os, double delta ) { os << delta << "s"; return os; };
static ostream& SStampAbsoluteRange( ostream &os, const CMaxTimerAbstraction &rO, const CMaxTimerAbstraction &rD ) { os << "[origin" << rO; os << ",duration" << rD << "]"; return os; }; };
/*********************************************************************************
/* Classes:
/* CMaxMiniProfiler_Node_Standard
/* Comments:
/********************************************************************************/ class CMaxMiniProfiler_Node_Standard : public CMaxMiniProfiler_Node_Base { public:
// comparison by full titles
// -------------------------
class CCompareFullTitles { public: MAXPROFNOTHROW bool operator()( const CMaxMiniProfiler_Node_Standard &o1, const CMaxMiniProfiler_Node_Standard &o2 ) const { assert( &o1 != &o2 ); return ( o1.m_oFullTitle < o2.m_oFullTitle ); }; }; friend CCompareFullTitles;
// comparison for node merging (a) level, b) full title, c) index)
// ---------------------------------------------------------------
class CCompareForNodeMerging { public: MAXPROFNOTHROW bool operator()( const CMaxMiniProfiler_Node_Standard &o1, const CMaxMiniProfiler_Node_Standard &o2 ) const { assert( &o1 != &o2 );
if ( o1.m_lLevel < o2.m_lLevel ) return true; else if ( o1.m_lLevel == o2.m_lLevel ) { if ( o1.m_oFullTitle < o2.m_oFullTitle ) return true; else if ( o1.m_oFullTitle == o2.m_oFullTitle ) { if ( o1.m_lIndex < o2.m_lIndex ) return true; } }
return false; }; }; friend CCompareForNodeMerging;
// for the unique algorithm; modifies the parameters
// -------------------------------------------------
class CMergeSimilarNodes { public: MAXPROFNOTHROW bool operator()( CMaxMiniProfiler_Node_Standard &o1, CMaxMiniProfiler_Node_Standard &o2 ) { assert( &o1 != &o2 );
if ( ( o1.m_lLevel == o2.m_lLevel ) && ( o1.m_oFullTitle == o2.m_oFullTitle ) ) { if ( o1.m_nCount > 0 && o2.m_nCount > 0 ) { CMaxMiniProfiler_Node_Standard &kept = ( o1.m_lIndex < o2.m_lIndex ) ? o1 : o2; CMaxMiniProfiler_Node_Standard &thrown = ( o1.m_lIndex < o2.m_lIndex ) ? o2 : o1;
kept.m_nCount++; kept.m_taDelta.FAdd( thrown.m_taDelta ); kept.m_dDelta = 0; thrown.m_nCount = 0; thrown.m_taDelta = CMaxTimerAbstraction(); thrown.m_dDelta = 0; }
return true; }
return false; }; }; friend CMergeSimilarNodes;
protected:
MMPNBString m_oFullTitle;
public:
// initialization
// --------------
MAXPROFNOTHROW void FInitialize( unsigned long lLevel, const char *pszTitle ) { m_lLevel = lLevel; m_pszTitle = pszTitle; };
MAXPROFNOTHROW void FIndex( unsigned long lIndex ) { m_lIndex = lIndex; };
MAXPROFNOTHROW void FSetFullTitle( const MMPNBString &roFullTitle ) { m_oFullTitle = roFullTitle; };
// access to members
// -----------------
MAXPROFNOTHROW const MMPNBString &roFGetFullTitle() const { return m_oFullTitle; }; };
/*********************************************************************************
/* Class:
/* CMaxMiniProfiler_Node_NoHistory
/* Comments:
/********************************************************************************/ class CMaxMiniProfiler_Node_NoHistory : public CMaxMiniProfiler_Node_Base { public:
// unique key to a profiler node
// -----------------------------
class CKey { public: unsigned long m_lLevel; ULONG_PTR m_lCheckSum; const char *m_pszTitle;
public: MAXPROFNOTHROW CKey( unsigned long lLevel = 0, const char *pszTitle = NULL, ULONG_PTR lCheckSum = 0 ) : m_lLevel( lLevel ) , m_lCheckSum( lCheckSum ) , m_pszTitle( pszTitle ) { }; };
// comparison of unique keys
// -------------------------
class CCompareKeys { public: MAXPROFNOTHROW bool operator()( const CKey &o1, const CKey &o2 ) const { assert( &o1 != &o2 ); if ( o1.m_lLevel < o2.m_lLevel ) return true; else if ( o1.m_lLevel == o2.m_lLevel ) { if ( o1.m_pszTitle < o2.m_pszTitle ) return true; else if ( o1.m_pszTitle == o2.m_pszTitle ) { if ( o1.m_lCheckSum < o2.m_lCheckSum ) return true; } }
return false; }; };
protected:
CMaxTimerAbstraction m_oInternalOverhead; ULONG_PTR m_lCheckSum;
public:
MAXPROFNOTHROW CMaxMiniProfiler_Node_NoHistory() : CMaxMiniProfiler_Node_Base() , m_lCheckSum( 0 ) { };
// initialization
// --------------
MAXPROFNOTHROW void FInitialize( unsigned long lLevel, const char *pszTitle, unsigned long lIndex, const CMaxTimerAbstraction oInternalOverhead ) { if ( 0 == m_lIndex ) { m_lLevel = lLevel; m_pszTitle = pszTitle; m_lIndex = lIndex; } #ifdef _DEBUG
else { assert( lLevel == m_lLevel ); assert( pszTitle == m_pszTitle ); } #endif
m_oInternalOverhead.FAdd( oInternalOverhead ); };
MAXPROFNOTHROW void FSetCheckSum( ULONG_PTR lCheckSum ) { m_lCheckSum = lCheckSum; };
// access to members
// -----------------
MAXPROFNOTHROW const CMaxTimerAbstraction &roFGetInternalOverhead() const { return m_oInternalOverhead; }; MAXPROFNOTHROW ULONG_PTR lFGetCheckSum() const { return m_lCheckSum; }; };
/*********************************************************************************
/* Class:
/* CMaxMiniProfiler_Base
/* Comments:
/********************************************************************************/ class CMaxMiniProfiler_Base { protected: // output file name
const char *m_poFileName;
// internal info
DWORD m_dwThreadId; CTime m_oStartTimeOfProfilings;
protected: // Note: the lock in CMaxMultithreadProfiler takes care of protecting
// the static data below in multithread mode
// default values for initialization
static const char *s_poDefaultFileName; static unsigned long s_lHeapBlockSize;
// BIAS values
static CMaxTimerAbstraction s_oOutOfBraceBiasApproximation; static CMaxTimerAbstraction s_oInOfBraceBiasApproximation; static bool s_bBiasIsKnown;
public:
// constructor / destructor
// ------------------------
CMaxMiniProfiler_Base( const TCHAR * = NULL ) : m_poFileName( s_poDefaultFileName ) , m_dwThreadId( ::GetCurrentThreadId() ) , m_oStartTimeOfProfilings( CTime::GetCurrentTime() ) { };
~CMaxMiniProfiler_Base() { };
// locking - public interface
// --------------------------
void FLockProfiler(){}; void FUnlockProfiler(){};
// bias approximation
// ------------------
// Note: the result of this operation is used at output time uniquely
bool bFIsBiasKnown() const { return s_bBiasIsKnown; };
protected:
// for final output
// ----------------
void FOutputEmptySession() { // open the output file
ofstream os( m_poFileName, ios::out | ios::ate );
// just stamp a message saying that there was nothing to profile
CTime t = CTime::GetCurrentTime(); os << endl; os << "PROFILER INSTANTIATED THE "; os << t.GetYear() << "/" << t.GetMonth() << "/" << t.GetDay() << " BETWEEN "; SStampCTime( os, m_oStartTimeOfProfilings ) << " AND "; SStampCTime( os, t ) << " WAS NOT USED." << endl; };
void FOutputHeaderCore( ostream &os, unsigned long lNumberOfOpenNodes, const CMaxMiniProfiler_Node_Base &roRootNode, unsigned long lTotalNumberOfNodes ) { // stamp the current time in our logfile
CTime t = CTime::GetCurrentTime(); os << endl; os << "***************************" << endl; os << "*** @@ProfilingDate=" << t.GetYear() << "/" << t.GetMonth() << "/" << t.GetDay() << endl; os << "*** @@ProfilingStartTime="; SStampCTime( os, m_oStartTimeOfProfilings ) << endl; os << "*** @@ProfilingEndTime="; SStampCTime( os, t ) << endl; os << "*** @@ProfilingRange="; roRootNode.FStampAbsoluteRange( os ); os << endl; if ( 0 != lNumberOfOpenNodes ) os << "*** "<< lNumberOfOpenNodes << " NODES WERE NOT CLOSED BY THE USER" << endl; os << "***************************" << endl;
// output the counter's frequency and thread id
os << "*** @@CounterFrequency=" << CMaxTimerAbstraction::oSFrequency() << endl; os << "*** @@ThreadId=" << ( unsigned long )m_dwThreadId << endl;
// output the profiler's finest possible unit of measurement
CMaxTimerAbstraction origin( 1 ), destination( 1 ); CMaxTimerAbstraction delta( CMaxTimerAbstraction::oSDifference( destination, origin ) ); os << "*** @@FinestMeasurement="; CMaxMiniProfiler_Node_Base::SStampDeltaInSeconds( os, delta.dFInSeconds() ) << "=" << delta << endl;
// output the profiler's approximated bias
assert( s_bBiasIsKnown ); os << "*** @@OutsideBias="; CMaxMiniProfiler_Node_Base::SStampDeltaInSeconds( os, s_oOutOfBraceBiasApproximation.dFInSeconds() ) << endl; os << "*** @@InsideBias="; CMaxMiniProfiler_Node_Base::SStampDeltaInSeconds( os, s_oInOfBraceBiasApproximation.dFInSeconds() ) << endl;
// output the total number of blocks
os << "*** @@TotalNumberOfBlocks=" << lTotalNumberOfNodes << endl; };
void FOutputMergedSectionHeader( ostream &os ) const { os << "*** @@MergedResults=" << endl; };
bool bFHistoryOutputDisabled() const { return bGIsEnabledEnvVar( MAX_ENV_DISABLE_PROFILING_HISTORY ); };
public: static ostream& SStampCTime( ostream &os, const CTime &roTime ) { os << roTime.GetHour() << ":" << roTime.GetMinute() << ":" << roTime.GetSecond(); return os; };
private: CMaxMiniProfiler_Base( const CMaxMiniProfiler_Base &o ); const CMaxMiniProfiler_Base& operator =( const CMaxMiniProfiler_Base & ); };
/*********************************************************************************
/* Functions:
/* GOutputProfilings
/* lGGetNumberOfProfilingSubNodes
/* lGDetermineMaxLevelOfProfilings
/* GRemoveInAndOutBiasFromProfilingNodes
/* Comments:
/* Done this way to avoid virtuals at node level (and to have common
/* output code for Standard and NoHistory profilers)
/********************************************************************************/ template <class TVectorItem> void GOutputProfilings( ostream &os, std::vector<TVectorItem> &roProfilings, unsigned long lMaxLevel, double dPrecisionThreshold, bool bOutputAbsoluteTimeRange ) { std::vector<TVectorItem>::iterator i; std::vector<TVectorItem>::size_type n; std::vector<std::vector<TVectorItem>::size_type> parents( 1 + lMaxLevel );
parents[ 0 ] = 0; for ( i = roProfilings.begin(), n = 0; roProfilings.end() != i; i++, n++ ) { // signal the validity of the node
assert( 0 != ( *i ).nFGetCount() ); os << ( ( ( ( *i ).dFGetDelta() / ( *i ).nFGetCount() ) < dPrecisionThreshold ) ? "X" : " " );
// output the node
( *i ).FOutput( os );
// register it as the last parent of its level
long currentlevel = ( *i ).lFGetLevel(); parents[ currentlevel ] = n;
// output the % for all parents of the node
os << " @@PERCENT="; double deltat = ( *i ).dFGetDelta(); for ( long j = currentlevel - 1; j >= 0; j-- ) os << 100.0 * deltat / roProfilings[ parents[ j ] ].dFGetDelta() << "% ";
// output the time range in units
if ( bOutputAbsoluteTimeRange ) { os << " @@Range="; ( *i ).FStampAbsoluteRange( os ); }
// finish output for this node
os << endl; } };
template <class TVectorItem, class TVectorIterator> unsigned long lGGetNumberOfProfilingSubNodes( const std::vector<TVectorItem> &roProfilings, TVectorIterator &roOrg ) { unsigned long level = ( *roOrg ).lFGetLevel(); unsigned long n; TVectorIterator i = roOrg; i++; for ( n = 0; roProfilings.end() != i; i++, n++ ) if ( ( *i ).lFGetLevel() <= level ) break;
return n; };
template <class TVectorItem> unsigned long lGDetermineMaxLevelOfProfilings( const std::vector<TVectorItem> &roProfilings ) { unsigned long l = 0; std::vector<TVectorItem>::const_iterator i;
for ( i = roProfilings.begin(); roProfilings.end() != i; i++ ) if ( ( *i ).lFGetLevel() > l ) l = ( *i ).lFGetLevel();
return l; };
template <class TVectorItem> void GRemoveInAndOutBiasFromProfilingNodes( std::vector<TVectorItem> &roProfilings, const CMaxTimerAbstraction &roOutOfBraceBiasApproximation, const CMaxTimerAbstraction &roInOfBraceBiasApproximation ) { std::vector<TVectorItem>::iterator i; unsigned long t, k;
for ( i = roProfilings.begin(); roProfilings.end() != i; i++ ) { CMaxTimerAbstraction &rtaDelta = ( *i ).roFGetDelta(); t = ::lGGetNumberOfProfilingSubNodes( roProfilings, i ); for ( k = 0; k < t; k++ ) rtaDelta.FSubstract( roOutOfBraceBiasApproximation ); for ( k = 0; k < t + 1; k++ ) rtaDelta.FSubstract( roInOfBraceBiasApproximation ); } };
/*********************************************************************************
/* Class:
/* CMaxMiniProfiler_Standard
/* Comments:
/********************************************************************************/ class CMaxMiniProfiler_Standard : public CMaxMiniProfiler_Base { protected: typedef std::vector<CMaxMiniProfiler_Node_Standard> MMPNodes; typedef MMPNodes::size_type MMPNodesRandomAccess; typedef std::vector<MMPNodesRandomAccess> MMPNodesReferences; typedef std::stack<MMPNodesRandomAccess, MMPNodesReferences> MMPStack; typedef MMPStack::size_type MMPStackSizeType;
protected: // profiling nodes
MMPNodes m_oProfilings; MMPNodesRandomAccess m_oLastNode; // stack for nested blocks
MMPStack m_oStack;
// heap acquisition timings
MMPNodes m_oHeapAcquisitionTimings;
public:
// constructor / destructor
// ------------------------
CMaxMiniProfiler_Standard( const TCHAR *pszSpecificEnabler = NULL ) : CMaxMiniProfiler_Base( pszSpecificEnabler ) , m_oProfilings( 0 ) , m_oLastNode( 0 ) , m_oHeapAcquisitionTimings( 0 ) { FInitDumpingSession(); };
~CMaxMiniProfiler_Standard() { FDumpSession(); FTermDumpingSession(); };
// dumping results - public interface
// ----------------------------------
void FDumpResults( bool bForced = false, bool = true ) { if ( !bForced ) { // can dump results only when all profiling nodes are closed
// (except the main one); we don't want to artificially close the nodes
// here at this point
if ( 1 != m_oStack.size() ) { assert( false ); return; } }
// dump
FDumpSession(); FTermDumpingSession();
// prepare for next dump
FInitDumpingSession(); };
// profiling nodes generation
// --------------------------
// Note: FCreateNewNode and FCloseCurrentNode are meant to be as fast as possible;
// also, the bracket between FStart and FStop is as small as possible
void FCreateNewNode( const char *pszTitle ) { assert( ( 0 == m_oStack.size() ) || ( ::GetCurrentThreadId() == m_dwThreadId ) );
if ( m_oProfilings.size() == m_oLastNode ) FReserveMoreHeap();
// Note: this is time constant
m_oStack.push( m_oLastNode ); CMaxMiniProfiler_Node_Standard &roNode = m_oProfilings[ m_oLastNode++ ]; roNode.FInitialize( static_cast<ULONG>(m_oStack.size()) - 1, pszTitle ); roNode.FStart(); };
void FCloseCurrentNode() { assert( ( 1 == m_oStack.size() ) || ( ::GetCurrentThreadId() == m_dwThreadId ) );
// Note: this is time constant
if ( m_oStack.size() > 0 ) { m_oProfilings[ m_oStack.top() ].FStop(); m_oStack.pop(); } else assert( false ); };
// bias approximation
// ------------------
// Note: the result of this operation is used at output time uniquely
void FSetBiasApproximationFrom( unsigned long lBiasSample ) { unsigned int i;
assert( !s_bBiasIsKnown );
// Note: this function should be called immediately after having created
// 1 BIAS (b) node
// and x NOTHINGNESS (N) subnodes (n1 ... nx),
// where x = lBiasSample
assert( m_oLastNode > 1 + lBiasSample ); // our out of brace bias is equal to (b - (n1 + n2 + ... + nx)) / x
s_oOutOfBraceBiasApproximation = m_oProfilings[ m_oLastNode - ( 1 + lBiasSample ) ].roFGetDelta(); for ( i = lBiasSample; i > 0; i-- ) s_oOutOfBraceBiasApproximation.FSubstract( m_oProfilings[ m_oLastNode - i ].roFGetDelta() ); s_oOutOfBraceBiasApproximation.FDivide( lBiasSample );
// our in of brace bias is equal to ((n1 + n2 + ... + nx) - N.x) / x
// Note: on purpose, we re-evaluate N as many times as there are samples
s_oInOfBraceBiasApproximation = CMaxTimerAbstraction(); CMaxTimerAbstraction delta; for ( i = lBiasSample; i > 0; i-- ) { CMaxTimerAbstraction origin( 1 ), destination( 1 ); delta.FAdd( CMaxTimerAbstraction::oSDifference( destination, origin ) ); s_oInOfBraceBiasApproximation.FAdd( m_oProfilings[ m_oLastNode - i ].roFGetDelta() ); } s_oInOfBraceBiasApproximation.FSubstract( delta ); s_oInOfBraceBiasApproximation.FDivide( lBiasSample );
#if 1
// remove those BIAS and NOTHINGNESS nodes from the profiler's output nodes
MMPNodes::iterator iter; MMPNodesRandomAccess n; for ( iter = m_oProfilings.begin(), n = 0; ( m_oProfilings.end() != iter ) && ( n < m_oLastNode - ( 1 + lBiasSample ) ); iter++, n++ ); std::fill( iter, m_oProfilings.end(), CMaxMiniProfiler_Node_Standard() ); m_oLastNode -= ( 1 + lBiasSample ); #endif
s_bBiasIsKnown = true; };
protected:
// dumping session management
// --------------------------
void FInitDumpingSession() { // prepare some heap
FReserveMoreHeap();
// put a main node
FCreateNewNode( MAX_PROFTAGNODE_TOP );
// verify that we start cleanly
assert( 1 == m_oStack.size() ); assert( 0 == m_oStack.top() ); };
void FDumpSession() { MMPStackSizeType lNumberOfOpenNodes;
// terminate our main node
FCloseCurrentNode();
// make sure all nodes are closed
lNumberOfOpenNodes = m_oStack.size(); while ( !m_oStack.empty() ) FCloseCurrentNode();
if ( m_oLastNode > 1 ) { unsigned long lMaxLevel;
// final trimming and initializations
FTrimProfilings(); FIndexProfilings(); lMaxLevel = ::lGDetermineMaxLevelOfProfilings( m_oProfilings ); FComputeFullTitles( lMaxLevel );
// open the output file
ofstream os( m_poFileName, ios::out | ios::ate );
// output the raw profilings
FOutputHeader( os, lNumberOfOpenNodes ); if ( !bFHistoryOutputDisabled() ) FOutputProfilings( os, true, lMaxLevel );
// merge nodes and output merged results
FMergeProfilings(); FOutputMergedSectionHeader( os ); FOutputProfilings( os, false, lMaxLevel ); } else FOutputEmptySession(); };
void FTermDumpingSession() { while ( !m_oStack.empty() ) m_oStack.pop();
m_oLastNode = 0; m_oProfilings.erase( m_oProfilings.begin(), m_oProfilings.end() ); m_oHeapAcquisitionTimings.erase( m_oHeapAcquisitionTimings.begin(), m_oHeapAcquisitionTimings.end() ); };
protected:
// for final output
// ----------------
void FOutputHeader( ostream &os, MMPStackSizeType lNumberOfOpenNodes ) { FOutputHeaderCore( os, static_cast<ULONG>(lNumberOfOpenNodes), m_oProfilings[ 0 ], static_cast<ULONG>(m_oLastNode) ); // output the total number of heap allocations
double dTotalTimeInAllocations = m_oHeapAcquisitionTimings[ 0 ].dFGetDelta(); for ( MMPNodes::iterator i = m_oHeapAcquisitionTimings.begin(); m_oHeapAcquisitionTimings.end() != i; i++ ) dTotalTimeInAllocations += ( *i ).dFGetDelta(); os << "*** @@TotalNumberOfHeapAllocations=" << static_cast<ULONG>(m_oHeapAcquisitionTimings.size()) << "="; CMaxMiniProfiler_Node_Base::SStampDeltaInSeconds( os, dTotalTimeInAllocations ) << endl;
// output the total profiling overhead
double dTotalOverhead = ( ( double )( m_oLastNode - 1.0 ) * s_oOutOfBraceBiasApproximation.dFInSeconds() ) + ( ( double )m_oLastNode * s_oInOfBraceBiasApproximation.dFInSeconds() ); double dTotalOverheadPercent = 100.0 * ( dTotalOverhead / ( dTotalOverhead + m_oProfilings[ 0 ].dFGetDelta() ) ); os << "*** @@TotalProfilerOverhead=" << dTotalOverheadPercent << "%="; CMaxMiniProfiler_Node_Base::SStampDeltaInSeconds( os, dTotalOverhead ) << endl;
// that's it
os << "***************************" << endl; os << "*** @@History=" << endl; };
void FOutputProfilings( ostream &os, bool bOutputAbsoluteTimeRange, unsigned long lMaxLevel ) { double dPrecisionThreshold = 2.0 * ( s_oOutOfBraceBiasApproximation.dFInSeconds() + s_oInOfBraceBiasApproximation.dFInSeconds() );
::GOutputProfilings( os, m_oProfilings, lMaxLevel, dPrecisionThreshold, bOutputAbsoluteTimeRange ); };
// final management of profiling nodes
// -----------------------------------
void FTrimProfilings() { MMPNodes::iterator i, j; MMPNodesRandomAccess n;
// find the iterator that corresponds to the last node
for ( i = m_oProfilings.begin(), n = 0; ( m_oProfilings.end() != i ) && ( n < m_oLastNode ); i++, n++ );
// remove uninitialized nodes
m_oProfilings.erase( i, m_oProfilings.end() );
// remove heap allocation timings from affected nodes
for ( i = m_oHeapAcquisitionTimings.begin(); m_oHeapAcquisitionTimings.end() != i; i++ ) for ( j = m_oProfilings.begin(); m_oProfilings.end() != j; j++ ) ( *j ).FConditionalRemove( *i, s_oOutOfBraceBiasApproximation );
// remove from nodes the profiling bias
::GRemoveInAndOutBiasFromProfilingNodes( m_oProfilings, s_oOutOfBraceBiasApproximation, s_oInOfBraceBiasApproximation ); };
void FIndexProfilings() { MMPNodes::iterator i; unsigned long n;
for ( i = m_oProfilings.begin(), n = 1; m_oProfilings.end() != i; i++, n++ ) ( *i ).FIndex( n ); };
void FComputeFullTitles( unsigned long lMaxLevel ) { MMPNodes::iterator i; MMPNodesRandomAccess j, n; MMPNodesReferences parents( 1 + lMaxLevel );
parents[ 0 ] = 0; for ( i = m_oProfilings.begin(), n = 0; m_oProfilings.end() != i; i++, n++ ) { // register the node as the last parent of its level
unsigned long currentlevel = ( *i ).lFGetLevel(); parents[ currentlevel ] = n;
// compute the iterated node's full title
CMaxMiniProfiler_Node_Base::MMPNBString fulltitle; for ( j = 0; j <= currentlevel; j++ ) fulltitle += CMaxMiniProfiler_Node_Base::MMPNBString( m_oProfilings[ parents[ j ] ].pszFGetTitle() ); ( *i ).FSetFullTitle( fulltitle ); } };
void FMergeProfilings() { MMPNodes::iterator i;
// sort by level/name/index
std::sort( m_oProfilings.begin(), m_oProfilings.end(), CMaxMiniProfiler_Node_Standard::CCompareForNodeMerging() );
// merge the nodes that have same level/name
i = std::unique( m_oProfilings.begin(), m_oProfilings.end(), CMaxMiniProfiler_Node_Standard::CMergeSimilarNodes() ); m_oProfilings.erase( i, m_oProfilings.end() );
// sort by full name
std::sort( m_oProfilings.begin(), m_oProfilings.end(), CMaxMiniProfiler_Node_Standard::CCompareFullTitles() ); };
protected:
// heap management
// ---------------
void FReserveMoreHeap() { CMaxMiniProfiler_Node_Standard node;
// log the time we used to generate new heap
node.FStart(); node.FInitialize( 0, MAX_PROFTAGNODE_HEAPALLOCATION );
// reserve a new chunk of nodes
m_oProfilings.reserve( m_oProfilings.size() + s_lHeapBlockSize ); m_oProfilings.insert( m_oProfilings.end(), m_oProfilings.capacity() - m_oProfilings.size(), CMaxMiniProfiler_Node_Standard() );
// that's it
m_oHeapAcquisitionTimings.push_back( node ); m_oHeapAcquisitionTimings.back().FStop(); };
private: CMaxMiniProfiler_Standard( const CMaxMiniProfiler_Standard &o ); const CMaxMiniProfiler_Standard& operator =( const CMaxMiniProfiler_Standard & ); };
/*********************************************************************************
/* Class:
/* CMaxMiniProfiler_NoHistory
/* Comments:
/* This implementation is targetted for massive amounts of nodes
/********************************************************************************/ class CMaxMiniProfiler_NoHistory : public CMaxMiniProfiler_Base { protected: typedef CMaxMiniProfiler_Node_NoHistory::CKey MMPNHKey; typedef CMaxMiniProfiler_Node_NoHistory::CCompareKeys MMPNHKeyCompare; typedef std::map<MMPNHKey, CMaxMiniProfiler_Node_NoHistory, MMPNHKeyCompare> MMPNHNodes; typedef MMPNHNodes::iterator MMPNHNodesIterator; typedef std::vector<MMPNHNodesIterator> MMPNHNodesReferences; typedef std::stack<MMPNHNodesIterator, MMPNHNodesReferences> MMPNHStack; typedef MMPNHStack::size_type MMPNHStackSizeType;
protected: typedef std::vector<CMaxMiniProfiler_Node_NoHistory> MMPNHFinalNodes; typedef MMPNHFinalNodes::iterator MMPNHFinalNodesIterator;
protected: // profiling nodes
MMPNHNodes m_oProfilings; unsigned long m_lLastNode; // stack for nested blocks
MMPNHStack m_oStack;
public:
// constructor / destructor
// ------------------------
CMaxMiniProfiler_NoHistory( const TCHAR *pszSpecificEnabler = NULL ) : CMaxMiniProfiler_Base( pszSpecificEnabler ) , m_lLastNode( 0 ) { FInitDumpingSession(); };
~CMaxMiniProfiler_NoHistory() { FDumpSession(); FTermDumpingSession(); };
// dumping results - public interface
// ----------------------------------
void FDumpResults( bool bForced = false, bool = true ) { if ( !bForced ) { // can dump results only when all profiling nodes are closed
// (except the main one); we don't want to artificially close the nodes
// here at this point
if ( 1 != m_oStack.size() ) { assert( false ); return; } }
// dump
FDumpSession(); FTermDumpingSession();
// prepare for next dump
FInitDumpingSession(); };
// profiling nodes generation
// --------------------------
// Note: FCreateNewNode and FCloseCurrentNode are meant to be as fast as possible;
// also, the bracket between FStart and FStop is as small as possible
void FCreateNewNode( const char *pszTitle ) { MMPNHNodesIterator i;
assert( ( 0 == m_oStack.size() ) || ( ::GetCurrentThreadId() == m_dwThreadId ) );
// A) this is not time constant
// ----------------------------
// Note: therefore we measure how much time we spend here
CMaxTimerAbstraction before( 1 ); { // compute the checksum
ULONG_PTR lCheckSum = ( ULONG_PTR )pszTitle; if ( !m_oStack.empty() ) lCheckSum += ( *m_oStack.top() ).first.m_lCheckSum;
// compute the key
MMPNHKey oKey( static_cast<unsigned long>(m_oStack.size()), pszTitle, lCheckSum );
// get the corresponding node, if any
i = m_oProfilings.find( oKey );
// otherwise, create a new node
if ( m_oProfilings.end() == i ) i = m_oProfilings.insert( MMPNHNodes::value_type( oKey, CMaxMiniProfiler_Node_NoHistory() ) ).first; } CMaxTimerAbstraction after( 1 );
// B) this is time constant
// ------------------------
// Note: therefore taken care of by bias computation
CMaxTimerAbstraction oInternalOverhead( CMaxTimerAbstraction::oSDifference( after, before ) );
m_lLastNode++; ( *i ).second.FInitialize( static_cast<unsigned long>(m_oStack.size()), pszTitle, m_lLastNode, oInternalOverhead ); m_oStack.push( i ); ( *i ).second.FStart(); };
void FCloseCurrentNode() { assert( ( 1 == m_oStack.size() ) || ( ::GetCurrentThreadId() == m_dwThreadId ) );
// Note: this is time constant
if ( m_oStack.size() > 0 ) { ( *m_oStack.top() ).second.FStop(); m_oStack.pop(); } else assert( false ); };
// bias approximation
// ------------------
// Note: the result of this operation is used at output time uniquely
void FSetBiasApproximationFrom( unsigned long lBiasSample ) { unsigned int i; MMPNHNodes::iterator j, j1, j2; CMaxTimerAbstraction b, n, ib;
assert( !s_bBiasIsKnown );
// Note: this function should be called immediately after having created
// 1 BIAS (b) node
// and x NOTHINGNESS (N) subnodes (n1 ... nx),
// where x = lBiasSample
assert( m_lLastNode > 1 + lBiasSample ); // find bias and nothingness nodes
// Note: here we search by name, because it's not time critical and
// we don't know the checksum
CMaxMiniProfiler_Node_Base::MMPNBString id_bias( MAX_PROFTAGNODE_BIAS ); CMaxMiniProfiler_Node_Base::MMPNBString id_nothingness( MAX_PROFTAGNODE_NOTHINGNESS ); char cDone = 0; for ( j = m_oProfilings.begin(); ( m_oProfilings.end() != j ) && ( ( 1 | 2 ) != cDone ); j++ ) { CMaxMiniProfiler_Node_Base::MMPNBString id_iterated( ( *j ).second.pszFGetTitle() );
if ( id_iterated == id_bias ) { assert( !( cDone & 1 ) ); b = ( *j ).second.roFGetDelta(); j1 = j; cDone |= 1; } else if ( id_iterated == id_nothingness ) { assert( !( cDone & 2 ) ); n = ( *j ).second.roFGetDelta(); ib = ( *j ).second.roFGetInternalOverhead(); j2 = j; cDone |= 2; } } assert( ( 1 | 2 ) == cDone ); if ( cDone & 1 ) m_oProfilings.erase( j1 ); if ( cDone & 2 ) m_oProfilings.erase( j2 );
// our out of brace bias is equal to (b - (n1 + n2 + ... + nx) - (ib1 + ib2 + ... + ibx)) / x
// Note: ib is the internal bias (or overhead), and is taken care of separately
s_oOutOfBraceBiasApproximation = b; s_oOutOfBraceBiasApproximation.FSubstract( n ); s_oOutOfBraceBiasApproximation.FSubstract( ib ); s_oOutOfBraceBiasApproximation.FDivide( lBiasSample );
// our in of brace bias is equal to ((n1 + n2 + ... + nx) - N.x) / x
// Note: on purpose, we re-evaluate N as many times as there are samples
CMaxTimerAbstraction delta; for ( i = lBiasSample; i > 0; i-- ) { CMaxTimerAbstraction origin( 1 ), destination( 1 ); delta.FAdd( CMaxTimerAbstraction::oSDifference( destination, origin ) ); } s_oInOfBraceBiasApproximation = n; s_oInOfBraceBiasApproximation.FSubstract( delta ); s_oInOfBraceBiasApproximation.FDivide( lBiasSample );
s_bBiasIsKnown = true; };
protected:
// dumping session management
// --------------------------
void FInitDumpingSession() { // put a main node
FCreateNewNode( MAX_PROFTAGNODE_TOP );
// verify that we start cleanly
assert( 1 == m_oStack.size() ); };
void FDumpSession() { MMPNHStackSizeType lNumberOfOpenNodes; MMPNHFinalNodes oFinalNodes; unsigned long lMaxLevel;
// terminate our main node
FCloseCurrentNode();
// make sure all nodes are closed
lNumberOfOpenNodes = m_oStack.size(); while ( !m_oStack.empty() ) FCloseCurrentNode();
// get the final list of nodes, sorted by index
FGetFinalNodes( oFinalNodes );
if ( oFinalNodes.size() > 1 ) { // final trimming and initializations
::GRemoveInAndOutBiasFromProfilingNodes( oFinalNodes, s_oOutOfBraceBiasApproximation, s_oInOfBraceBiasApproximation ); lMaxLevel = ::lGDetermineMaxLevelOfProfilings( oFinalNodes ); CMaxTimerAbstraction oTotalInternalOverhead = oFRemoveInternalOverheadFromFinalNodes( oFinalNodes );
// open the output file
ofstream os( m_poFileName, ios::out | ios::ate );
// output the raw profilings
FOutputHeader( oFinalNodes, os, lNumberOfOpenNodes, oTotalInternalOverhead ); FOutputFinalNodes( oFinalNodes, os, lMaxLevel ); } else FOutputEmptySession(); };
void FTermDumpingSession() { while ( !m_oStack.empty() ) m_oStack.pop();
m_oProfilings.erase( m_oProfilings.begin(), m_oProfilings.end() ); };
protected:
// for final output
// ----------------
void FOutputHeader( MMPNHFinalNodes &roFinalNodes, ostream &os, MMPNHStackSizeType lNumberOfOpenNodes, const CMaxTimerAbstraction &roTotalInternalOverhead ) { FOutputHeaderCore( os, static_cast<unsigned long>(lNumberOfOpenNodes), roFinalNodes[ 0 ], m_lLastNode );
// output the total profiling overhead
double dTotalOverhead = ( ( double )( m_lLastNode - 1.0 ) * s_oOutOfBraceBiasApproximation.dFInSeconds() ) + ( ( double )m_lLastNode * s_oInOfBraceBiasApproximation.dFInSeconds() ) + roTotalInternalOverhead.dFInSeconds(); double dTotalOverheadPercent = 100.0 * ( dTotalOverhead / ( dTotalOverhead + roFinalNodes[ 0 ].dFGetDelta() ) ); os << "*** @@TotalProfilerOverhead=" << dTotalOverheadPercent << "%="; CMaxMiniProfiler_Node_Base::SStampDeltaInSeconds( os, dTotalOverhead ) << endl;
// that's it
os << "***************************" << endl; FOutputMergedSectionHeader( os ); };
// final management of profiling nodes
// -----------------------------------
void FGetFinalNodes( MMPNHFinalNodes &roFinalNodes ) { assert( !m_oProfilings.empty() );
// copy the map of profiling nodes into a simple vector
for ( MMPNHNodes::iterator i = m_oProfilings.begin(); m_oProfilings.end() != i; i++ ) { ( *i ).second.FSetCheckSum( ( *i ).first.m_lCheckSum ); roFinalNodes.push_back( ( *i ).second ); }
// sort the vector by nodes indexes
std::sort( roFinalNodes.begin(), roFinalNodes.end(), CMaxMiniProfiler_Node_Base::CCompareIndexes() );
// reparent the lost nodes
// Note: sorting by nodes indexes is not good enough when the profiled code has some
// conditional branches; suppose a new node appears in a branch, its index might
// be greater than nodes that don't belong to that branch; therefore reparenting
// those lost nodes is necessary
// Note: top node doesn't have a parent, so skip it
// Note: this algorithm is O(n2) right now, and could be improved, but since it is
// executed at output time only, I don't care
MMPNHFinalNodesIterator j = roFinalNodes.begin(); j++; while ( roFinalNodes.end() != j ) { const MMPNHFinalNodesIterator oldj = j; bool bWrongParent = false; unsigned long lTargetLevel = ( *j ).lFGetLevel() - 1; ULONG_PTR lTargetCheckSum = ( *j ).lFGetCheckSum() - ( ULONG_PTR )( *j ).pszFGetTitle();
// find the real parent of j (must appear before j in the sorted vector)
for ( MMPNHFinalNodesIterator k = j; roFinalNodes.end() != k; k-- ) { unsigned long lIteratedLevel = ( *k ).lFGetLevel();
// the real parent must have a level equal to lTargetLevel
if ( lIteratedLevel != lTargetLevel ) { // maybe j didn't even have an immediate wrong parent
if ( lIteratedLevel < lTargetLevel ) bWrongParent = true; continue; }
// the parent must have a checksum equal to lTargetCheckSum,
// otherwise it is a wrong parent
if ( ( *k ).lFGetCheckSum() != lTargetCheckSum ) bWrongParent = true;
// we found the real parent
else { // if no wrong parent was encountered, nothing to do
if ( !bWrongParent ) { j++; break; }
// otherwise, we must move the node below its real parent
else { CMaxMiniProfiler_Node_NoHistory nodecopy = *j;
j++; k++; roFinalNodes.erase( oldj ); roFinalNodes.insert( k, nodecopy );
bWrongParent = false; break; } } }
assert( !bWrongParent ); assert( oldj != j ); } }
CMaxTimerAbstraction oFRemoveInternalOverheadFromFinalNodes( MMPNHFinalNodes &roFinalNodes ) { CMaxTimerAbstraction oTotalOverhead; MMPNHFinalNodes::iterator i; std::vector<MMPNHFinalNodesIterator> parents; std::vector<MMPNHFinalNodesIterator>::iterator j; unsigned long l, s;
for ( i = roFinalNodes.begin(); roFinalNodes.end() != i; i++ ) { // get the current node level (l) and stack of parents size (s)
l = ( *i ).lFGetLevel(); s = static_cast<unsigned long>(parents.size());
// get the iterated node's internal overhead
const CMaxTimerAbstraction &roOverhead = ( *i ).roFGetInternalOverhead(); oTotalOverhead.FAdd( roOverhead );
// update the stack of parents
if ( s > 0 ) { while ( s > l ) { parents.pop_back(); s--; } } assert( l == s );
// remove internal overhead from all parents
for ( j = parents.begin(); parents.end() != j; j++ ) { assert( ( *j ) != i ); CMaxTimerAbstraction &rtaDelta = ( *( *j ) ).roFGetDelta(); rtaDelta.FSubstract( roOverhead ); }
// insert the current node in the stack of parents
parents.push_back( i ); }
return oTotalOverhead; };
void FOutputFinalNodes( MMPNHFinalNodes &roFinalNodes, ostream &os, unsigned long lMaxLevel ) { double dPrecisionThreshold = 2.0 * ( s_oOutOfBraceBiasApproximation.dFInSeconds() + s_oInOfBraceBiasApproximation.dFInSeconds() );
::GOutputProfilings( os, roFinalNodes, lMaxLevel, dPrecisionThreshold, false ); };
private: CMaxMiniProfiler_NoHistory( const CMaxMiniProfiler_NoHistory &o ); const CMaxMiniProfiler_NoHistory& operator =( const CMaxMiniProfiler_NoHistory & ); };
/*********************************************************************************
/* Class:
/* CMaxMultithreadProfiler
/* Comments:
/* Instantiates and manages one CMaxMiniProfiler per calling thread
/********************************************************************************/ template <class TMiniProfiler> class CMaxMultithreadProfiler { protected: typedef std::less<DWORD> MTPThreadIdsCompare; typedef std::map<DWORD, TMiniProfiler*, MTPThreadIdsCompare> MTPMap;
protected: class __CMaxCriticalSection { protected: CRITICAL_SECTION m_oNTCriticalSection;
public: __CMaxCriticalSection(){ ::InitializeCriticalSection( &m_oNTCriticalSection ); }; ~__CMaxCriticalSection(){ ::DeleteCriticalSection( &m_oNTCriticalSection ); };
bool Lock() const { ::EnterCriticalSection( &( ( __CMaxCriticalSection * )this )->m_oNTCriticalSection ); return true; }; bool Unlock() const { ::LeaveCriticalSection( &( ( __CMaxCriticalSection * )this )->m_oNTCriticalSection ); return true; }; operator CRITICAL_SECTION*() const { return ( CRITICAL_SECTION* )&m_oNTCriticalSection; }; };
protected: MTPMap m_oProfilers; __CMaxCriticalSection m_oLockProfilers;
public: CMaxMultithreadProfiler( const TCHAR * = NULL ) { m_oProfilers[ ::GetCurrentThreadId() ] = new TMiniProfiler(); };
~CMaxMultithreadProfiler() { if ( !m_oProfilers.empty() ) FFlushProfilers(); };
void FLockProfiler() { m_oLockProfilers.Lock(); };
void FUnlockProfiler() { m_oLockProfilers.Unlock(); };
void FDumpResults( bool bForced = false, bool bCurrentThreadOnly = true ) { m_oLockProfilers.Lock(); { DWORD id = ::GetCurrentThreadId(); MTPMap::iterator i;
if ( m_oProfilers.empty() ) { m_oLockProfilers.Unlock(); return; }
for ( i = m_oProfilers.begin(); m_oProfilers.end() != i; i++ ) if ( !bCurrentThreadOnly || ( ( *i ).first == id ) ) ( *i ).second->FDumpResults( bForced );
if ( bForced ) FFlushProfilers(); } m_oLockProfilers.Unlock(); };
void FCreateNewNode( const char *pszTitle ) { m_oLockProfilers.Lock(); { DWORD id = ::GetCurrentThreadId(); MTPMap::iterator i = m_oProfilers.find( id );
if ( m_oProfilers.end() != i ) ( *i ).second->FCreateNewNode( pszTitle ); else { TMiniProfiler *pNewProfiler = new TMiniProfiler(); m_oProfilers[ id ] = pNewProfiler; pNewProfiler->FCreateNewNode( pszTitle ); } } m_oLockProfilers.Unlock(); }; void FCloseCurrentNode() { m_oLockProfilers.Lock(); { DWORD id = ::GetCurrentThreadId(); MTPMap::iterator i = m_oProfilers.find( id );
assert( m_oProfilers.end() != i ); ( *i ).second->FCloseCurrentNode(); } m_oLockProfilers.Unlock(); };
bool bFIsBiasKnown() const { bool b;
m_oLockProfilers.Lock(); assert( !m_oProfilers.empty() ); b = ( *m_oProfilers.begin() ).second->bFIsBiasKnown(); m_oLockProfilers.Unlock();
return b; };
void FSetBiasApproximationFrom( unsigned long lBiasSample ) { m_oLockProfilers.Lock(); assert( !m_oProfilers.empty() ); ( *m_oProfilers.begin() ).second->FSetBiasApproximationFrom( lBiasSample ); m_oLockProfilers.Unlock(); };
protected: void FFlushProfilers() { m_oLockProfilers.Lock(); assert( !m_oProfilers.empty() ); for ( MTPMap::iterator i = m_oProfilers.begin(); m_oProfilers.end() != i; i++ ) delete ( *i ).second; m_oProfilers.erase( m_oProfilers.begin(), m_oProfilers.end() ); m_oLockProfilers.Unlock(); };
private: CMaxMultithreadProfiler( const CMaxMultithreadProfiler &o ); const CMaxMultithreadProfiler& operator =( const CMaxMultithreadProfiler & ); }; #endif // }
#ifdef MAX_PROFILING_ENABLED_DLL // {
/*********************************************************************************
/* Class:
/* CMaxProfilingDLLWrapper
/* Comments:
/* For simplified use through the macros defined above
/********************************************************************************/ class CMaxProfilingDLLWrapper { protected: class __CMaxLoadLibrary { protected: HINSTANCE m_hLibrary;
public: __CMaxLoadLibrary( LPCTSTR pszLibraryFileName ) : m_hLibrary( NULL ) { if ( NULL != pszLibraryFileName ) m_hLibrary = ::LoadLibrary( pszLibraryFileName ); };
~__CMaxLoadLibrary() { if ( NULL != m_hLibrary ) ::FreeLibrary( m_hLibrary ); };
operator HINSTANCE() const { return m_hLibrary; }; };
protected: __CMaxLoadLibrary m_oLibrary;
protected: void ( *m_pfn_LockProfiler )(); void ( *m_pfn_UnlockProfiler )(); void ( *m_pfn_DumpResults )( bool, bool ); void ( *m_pfn_CreateNewNode )( const char * ); void ( *m_pfn_CloseCurrentNode )(); bool ( *m_pfn_IsBiasKnown )(); void ( *m_pfn_SetBiasApproximationFrom )( unsigned long );
protected: static void SLockProfiler_Bogus(){}; static void SUnlockProfiler_Bogus(){}; static void SDumpResults_Bogus( bool, bool ){}; static void SCreateNewNode_Bogus( const char * ){}; static void SCloseCurrentNode_Bogus(){}; static bool bSIsBiasKnown_Bogus(){ return true; }; static void SSetBiasApproximationFrom_Bogus( unsigned long ){};
public: CMaxProfilingDLLWrapper( const TCHAR *pszSpecificEnabler = NULL ) #ifndef unix
: m_oLibrary( _T( "s:\\ds\\util\\maxprof.dll" ) ) #else
: m_oLibrary( NULL ) #endif
, m_pfn_LockProfiler( NULL ) , m_pfn_UnlockProfiler( NULL ) , m_pfn_DumpResults( NULL ) , m_pfn_CreateNewNode( NULL ) , m_pfn_CloseCurrentNode( NULL ) , m_pfn_IsBiasKnown( NULL ) , m_pfn_SetBiasApproximationFrom( NULL ) { // if the profiler is enabled, get the dll's entry points for profiling
// (if possible)
if ( bFProfilerEnabled( pszSpecificEnabler ) && ( NULL != ( HINSTANCE )m_oLibrary ) ) { m_pfn_LockProfiler = ( void ( * )() )::GetProcAddress( m_oLibrary, "LockProfiler" ); assert( NULL != m_pfn_LockProfiler );
m_pfn_UnlockProfiler = ( void ( * )() )::GetProcAddress( m_oLibrary, "UnlockProfiler" ); assert( NULL != m_pfn_UnlockProfiler ); m_pfn_DumpResults = ( void ( * )( bool, bool ) )::GetProcAddress( m_oLibrary, "DumpResults" ); assert( NULL != m_pfn_DumpResults );
m_pfn_CreateNewNode = ( void ( * )( const char * ) )::GetProcAddress( m_oLibrary, "CreateNewNode" ); assert( NULL != m_pfn_CreateNewNode );
m_pfn_CloseCurrentNode = ( void ( * )() )::GetProcAddress( m_oLibrary, "CloseCurrentNode" ); assert( NULL != m_pfn_CloseCurrentNode );
m_pfn_IsBiasKnown = ( bool ( * )() )::GetProcAddress( m_oLibrary, "IsBiasKnown" ); assert( NULL != m_pfn_IsBiasKnown );
m_pfn_SetBiasApproximationFrom = ( void ( * )( unsigned long ) )::GetProcAddress( m_oLibrary, "SetBiasApproximationFrom" ); assert( NULL != m_pfn_SetBiasApproximationFrom ); }
// otherwise, create bogus entry points
// Note: this technique is preferred to using "if"s on each call, so this
// switch does not affect the profiling mode at all
else { m_pfn_LockProfiler = SLockProfiler_Bogus; m_pfn_UnlockProfiler = SUnlockProfiler_Bogus; m_pfn_DumpResults = SDumpResults_Bogus; m_pfn_CreateNewNode = SCreateNewNode_Bogus; m_pfn_CloseCurrentNode = SCloseCurrentNode_Bogus; m_pfn_IsBiasKnown = bSIsBiasKnown_Bogus; m_pfn_SetBiasApproximationFrom = SSetBiasApproximationFrom_Bogus; } };
// Note: this is the easiest way to avoid a severe bug in the DLL version of the
// profiler; basically, if a client DLL detaches the profiled process, the
// "titles" maintained by address by the profiling nodes become invalid, and
// can no longer be dereferenced; therefore, to avoid this problem, I make
// sure all profiling nodes are dumped when a client DLL detaches. This
// may affect results in some circumstances, but should be OK in most cases
~CMaxProfilingDLLWrapper() { FDumpResults( true, false ); };
void FLockProfiler() { ( *m_pfn_LockProfiler )(); }; void FUnlockProfiler() { ( *m_pfn_UnlockProfiler )(); }; void FDumpResults( bool bForced = false, bool bCurrentThreadOnly = true ) { ( *m_pfn_DumpResults )( bForced, bCurrentThreadOnly ); }; void FCreateNewNode( const char *pszTitle ) { ( *m_pfn_CreateNewNode )( pszTitle ); }; void FCloseCurrentNode() { ( *m_pfn_CloseCurrentNode )(); }; bool bFIsBiasKnown() const { return ( *m_pfn_IsBiasKnown )(); }; void FSetBiasApproximationFrom( unsigned long lBiasSample ) { ( *m_pfn_SetBiasApproximationFrom )( lBiasSample ); };
protected: #ifdef MAX_PROFILING_CONDITIONAL
bool bFProfilerEnabled( const TCHAR *pszSpecificEnabler ) const { // Note: the global enabler allows you to enable/disable
// all "subsystems" at once
// Note: the specific enabler allows you to enable/disable
// specific "subsystems", given that the global
// enabler is set
return ( bGIsEnabledEnvVar( MAX_ENV_ENABLE_PROFILING, MAX_ENV_ALL ) || ( bGIsEnabledEnvVar( pszSpecificEnabler ) && bGIsEnabledEnvVar( MAX_ENV_ENABLE_PROFILING ) ) ); } #else
bool bFProfilerEnabled( const TCHAR * ) const { return true; } #endif
}; #endif // }
#if defined MAX_PROFILING_ENABLED || defined MAX_PROFILING_ENABLED_DLL // {
//#ifdef _DEBUG
// #pragma message( "MAXPROFILER Warning: beware of profilings generated with a DEBUG build." )
//#endif
/*********************************************************************************
/* Class:
/* CMaxProfilingObject
/* Comments:
/* For simplified use through the macros defined above. The typedef
/* allows easy substitution between multi-threaded (default) and
/* single-threaded profilers
/********************************************************************************/ class CMaxProfilingObject { public: #ifdef MAX_PROFILING_ENABLED_DLL
typedef CMaxProfilingDLLWrapper MPOProfiler; #else
typedef CMaxMultithreadProfiler<CMaxMiniProfiler_Standard> MPOProfiler; #endif
protected: static MPOProfiler s_oProfiler;
protected: class __CBiasApproximation { public: __CBiasApproximation() { const unsigned long lBiasSample = 20;
// if bias has already been computed once, do nothing
if ( CMaxProfilingObject::s_oProfiler.bFIsBiasKnown() ) return;
// compute bias through the used profiler
CMaxProfilingObject::s_oProfiler.FLockProfiler(); { CMaxProfilingObject::SCreateNewNode( MAX_PROFTAGNODE_BIAS ); for ( int i = 0; i < lBiasSample; i++ ) { CMaxProfilingObject::SCreateNewNode( MAX_PROFTAGNODE_NOTHINGNESS ); CMaxProfilingObject::SCloseCurrentNode(); } CMaxProfilingObject::SCloseCurrentNode();
CMaxProfilingObject::s_oProfiler.FSetBiasApproximationFrom( lBiasSample ); } CMaxProfilingObject::s_oProfiler.FUnlockProfiler(); }; }; friend __CBiasApproximation;
protected: static __CBiasApproximation s_oBiasApproximation;
public: static void SDumpResults( bool bForced = false, bool bCurrentThreadOnly = true ) { s_oProfiler.FDumpResults( bForced, bCurrentThreadOnly ); }; static void SCreateNewNode( const char *pszTitle ) { s_oProfiler.FCreateNewNode( pszTitle ); }; static void SCloseCurrentNode() { s_oProfiler.FCloseCurrentNode(); }; }; #endif // }
#ifndef MAX_PROFILING_DLL_IMPLEMENTATION // {
/*********************************************************************************
/* Class:
/* CMaxProfilingBlockWrapper
/* Comments:
/* As a substitute to the macros (Ray's request). Hoping that those inlines
/* disappear completely when profiling is turned off. The alternative
/* would have been to define a new set of macros taking an additional
/* parameter (a unique name for each instance of the wrapper within the
/* same scope)
/* Note:
/* I use nothrow here, but the current implementations don't guaranty
/* that no exception will be thrown
/********************************************************************************/ class CMaxProfilingBlockWrapper { public: MAXPROFNOTHROW CMaxProfilingBlockWrapper( const char *pszTitle ) { BEGIN_PROFILING_BLOCK( pszTitle ); }; MAXPROFNOTHROW ~CMaxProfilingBlockWrapper() { END_PROFILING_BLOCK; };
public: MAXPROFNOTHROW static void SDump() { DUMP_PROFILING_RESULTS; }; }; #endif // }
/*********************************************************************************
/* Comments:
/* Here is the code used to generate maxprof.dll
/*********************************************************************************
#define MAX_PROFILING_DLL_IMPLEMENTATION
#include <iomanip.h>
#include <profile.h>
#pragma warning( disable : 4786 )
__MAX_MINIPROFILER_IMPLEMENTATION typedef CMaxMultithreadProfiler<CMaxMiniProfiler_NoHistory> GDllProfiler_Type1; typedef CMaxMultithreadProfiler<CMaxMiniProfiler_Standard> GDllProfiler_Type2; GDllProfiler_Type1 g_oProfiler; __declspec(dllexport) void LockProfiler() { g_oProfiler.FLockProfiler(); } __declspec(dllexport) void UnlockProfiler() { g_oProfiler.FUnlockProfiler(); } __declspec(dllexport) void DumpResults( bool bForced, bool bCurrentThreadOnly ) { g_oProfiler.FDumpResults( bForced, bCurrentThreadOnly ); } __declspec(dllexport) void CreateNewNode( const char *pszTitle ) { g_oProfiler.FCreateNewNode( pszTitle ); } __declspec(dllexport) void CloseCurrentNode() { g_oProfiler.FCloseCurrentNode(); } __declspec(dllexport) bool IsBiasKnown() { return g_oProfiler.bFIsBiasKnown(); } __declspec(dllexport) void SetBiasApproximationFrom( unsigned long lBiasSample ) { g_oProfiler.FSetBiasApproximationFrom( lBiasSample ); } #pragma warning( default : 4786 )
/*********************************************************************************
/* Comments:
/* Here is the DEF file used to generate maxprof.dll
/*********************************************************************************
EXPORTS LockProfiler @1 UnlockProfiler @2 DumpResults @3 CreateNewNode @4 CloseCurrentNode @5 IsBiasKnown @6 SetBiasApproximationFrom @7 /********************************************************************************/
// Note: I don't reenable C4786, and I know it...
#endif // }
|