JD2022-TU1/main/extern/gear4/gear_testing/performance/perftest_tester.h

231 lines
8.6 KiB
C++

#ifndef _GEAR_TESTING__PERFTEST_TESTER_H_
#define _GEAR_TESTING__PERFTEST_TESTER_H_
#include "perftest_delegate.h"
#include "perftest_logger.h"
#include "perftest_memory.h"
#include <gear_core/services/random/random.h>
#if G4_PLATFORM == G4_PLATFORM_PS3_SPU
#define printf spu_printf
#endif
namespace G4
{
//
// *** FunctionTester ***
// This is where the testing happens!
// - A set of values is generated as input for multiple iterations of a function.
// - A function is called multiple times in a very tight loop via a delegate.
// - Timing code is used to measure the performance of the function
//
template < typename _DELEGATE >
class FunctionTester
{
public:
FunctionTester( const G4::GearString& a_FuncName );
~FunctionTester();
typedef typename _DELEGATE::Traits DelegateTraits;
typedef typename DelegateTraits::ArgList ArgList;
template < typename _GENERATOR_TYPE >
void Run( _GENERATOR_TYPE& a_Generator );
template < typename _GENERATOR_TYPE >
bool Calibrate( _GENERATOR_TYPE& a_Generator, unsigned int a_iNumIterations = 1 );
G4_NOINLINE void ExecuteTest( void );
protected:
ArgList* m_TestValues; // All of the input used to test a function. Also includes storage space for the return value.
G4::U32 m_iTestIterations; // The number of times the function is run during testing
G4::StopWatch m_Timer;
G4::GearString m_FuncName;
static const G4::U32 s_iMinIterations = 100; // The minimum number of iterations over which to test a function
static const G4::U32 s_iMinCalibrationTime = 1; // The minimum time over which to calibrate a function
static const G4::U32 s_iTestTimeLimit = 500; // The approximate time (in msec) that a test is run for
static const G4::U32 s_iMaxCalibrationIterations;
private:
};
template < typename _DELEGATE >
const G4::U32 FunctionTester< _DELEGATE >::s_iMaxCalibrationIterations = std::numeric_limits< unsigned int >::max() >> 2;
template < typename _DELEGATE >
FunctionTester< _DELEGATE >::FunctionTester( const G4::GearString& a_FuncName )
: m_TestValues( NULL )
, m_FuncName( a_FuncName )
{
}
template < typename _DELEGATE >
FunctionTester< _DELEGATE >::~FunctionTester()
{
if ( m_TestValues != NULL )
{
G4::PerfTestMemoryPool::Instance().Release< ArgList >();
m_TestValues = NULL;
}
}
// return a seed corresponding to the current date
inline eal_u64 GetTodaySeed()
{
G4::Time::Date date;
G4::Time::GetDate(date);
return date.year * date.mon * date.mon * date.mday * date.mday;
}
//
// Estimate how many iterations of the test could be executed in the time allotted.
// Here we get an approximation of how long it takes to invoke the function. It is difficult to know how many times to execute
// the function during calibration as some functions take a very long time to execute and some are too fast to be accurately measured (do we calibrate the calibration?)
//
// The problem is solved as follows: The calibration starts by executing the function once, and then keeps doubling the number of iterations
// until the time to execute rises above a specific threshold. This result is then used to compute how many iterations are used in the full test.
// Calibration fails if the function takes too long to execute for a predefined minimum number of iterations.
//
template < typename _DELEGATE >
template < typename _GENERATOR_TYPE >
bool FunctionTester< _DELEGATE >::Calibrate( _GENERATOR_TYPE& a_Generator, unsigned int a_iNumIterations )
{
m_iTestIterations = a_iNumIterations;
G4::U64 cpuFrequency = G4::Time::GetCpuFrequency();
m_TestValues = G4::PerfTestMemoryPool::Instance().Acquire< ArgList >( m_iTestIterations );
if ( m_TestValues )
{
eal_u64 seed = GetTodaySeed(); // Seed the random generator before calibrating. All tests start with the same seed.
a_Generator.Init( seed );
G4::Random::SetSeed( static_cast<G4::S32>(seed) ); // every random operations used will be seeded with the same value
bool generated = TestValueGenerator< _DELEGATE >::Generate( a_Generator, m_TestValues, m_iTestIterations );
if ( !generated )
{
// Could not generate enough test values to adequately test this function
G4::PerfTestMemoryPool::Instance().Release< ArgList >();
m_TestValues = NULL;
}
}
if ( m_TestValues == NULL )
{
TEST_TRACE( "Could not generate enough values to calibrate the test." );
EXPECT_TRUE( m_TestValues != NULL );
// Could not allocate enough test values to adequately test this function
m_iTestIterations = 0;
return( false );
}
G4::U64 startClock = G4::Time::GetCpuCycle();
ExecuteTest();
G4::U64 elapsedCycles = G4::Time::GetElapsedCPUCycles( startClock ) * 1000; // Pre-multiply by 1000 so we get msec
float elapsedTime = (float)elapsedCycles / (float)cpuFrequency;
G4::PerfTestMemoryPool::Instance().Release< ArgList >();
m_TestValues = NULL;
// How many copies of the function's arguments and return value would fit in the cache?
// This is used to place an upper limit on the number of function iterations.
int cacheSize = static_cast<int>(G4_HW_L2_CACHE_SIZE());
if ( cacheSize > 0 )
{
G4::U32 numIterationsInCache = cacheSize / sizeof( ArgList );
if ( m_iTestIterations > numIterationsInCache )
return( true );
}
if ( ( elapsedTime > s_iTestTimeLimit ) && ( m_iTestIterations < s_iMinIterations ) )
{
// This function takes too long. We'll have to test it some other way
m_iTestIterations = 0;
return( false );
}
else if ( ( ( elapsedTime < s_iMinCalibrationTime ) || ( m_iTestIterations < s_iMinIterations ) ) && ( m_iTestIterations < s_iMaxCalibrationIterations ) )
{
assert( m_iTestIterations != 0 );
return( Calibrate( a_Generator, m_iTestIterations * 2 ) );
}
return( true );
}
//
// Test a function by repeatedly invoking it through a delegate
// A generator is provided that can create input for the function
//
template < typename _DELEGATE >
template < typename _GENERATOR_TYPE >
void FunctionTester< _DELEGATE >::Run( _GENERATOR_TYPE& a_Generator )
{
bool calibrated = Calibrate( a_Generator ); // Estimate how many times the function should be invoked during testing
if ( !calibrated )
{
TEST_TRACE( "Could not calibrate the test, probably because the function takes a long time to execute" );
EXPECT_TRUE( calibrated );
return;
}
m_TestValues = G4::PerfTestMemoryPool::Instance().Acquire< ArgList >( m_iTestIterations );
eal_u64 seed = GetTodaySeed(); // Reseed the random generator post-calibration. All tests start with the same seed.
TEST_TRACE( "Original Seed # " << seed );
if ( m_TestValues != NULL )
{
a_Generator.Init( seed );
G4::Random::SetSeed( static_cast<G4::S32>(seed) ); // every random operations used will be seeded with the same value
bool generated = TestValueGenerator< _DELEGATE >::Generate( a_Generator, m_TestValues, m_iTestIterations ); // Generate test data
if ( !generated )
{
G4::PerfTestMemoryPool::Instance().Release< ArgList >();
m_TestValues = NULL;
}
}
if ( m_TestValues == NULL )
{
TEST_TRACE( "Could not generate enough values to run the test." );
EXPECT_TRUE( m_TestValues != NULL );
// Could not allocate enough test values to adequately test this function
m_iTestIterations = 0;
return;
}
// Run the test!
m_Timer.Reset();
m_Timer.Start();
ExecuteTest();
m_Timer.Stop();
G4::U64 elapsed = m_Timer.GetElapsedUSec();
//printf( "\t%d iterations -- %lu usec\n", m_iTestIterations, elapsed );
G4::PerfTestLogger::Instance().LogTestResult(elapsed/static_cast<float>(m_iTestIterations));
G4::PerfTestMemoryPool::Instance().Release< ArgList >();
m_TestValues = NULL;
}
// Call the function repeatedly
// This is a tight loop that does nothing but invoke the function as fast as possible.
//
// Note that this is in a NOINLINE function.
// We want to keep everything inside of here isolated from its calling context
template < typename _DELEGATE >
G4_NOINLINE void FunctionTester< _DELEGATE >::ExecuteTest( void )
{
for ( unsigned int i = 0; i < m_iTestIterations; ++i )
{
typename DelegateTraits::ArgList& testvals = m_TestValues[ i ];
_DELEGATE::Invoke( testvals );
}
}
} // namespace G4
#endif // _GEAR_TESTING__PERFTEST_TESTER_H_