2358 lines
67 KiB
C++
2358 lines
67 KiB
C++
/*********************************************************************************
|
|
/* 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 // }
|