1
0
forked from mirrors/0ad

robustify CPU freq detection and timer code, hopefully solve lockup issue

cpu: cache CPU info to prevent calling freq measurement code several
times
config: mention e.g. fopen_s instead of only the secure string functions
ia32: exception-safe scheduler setting; no longer use absolute max
priority (risky, could hang machine if loop contains a bug)
wtime: add note on TSC safety, slight improvements
timer: try and prevent timer from returning the same value

This was SVN commit r5075.
This commit is contained in:
janwas
2007-05-16 15:56:47 +00:00
parent 2e5c530c03
commit 160bd14bb0
7 changed files with 308 additions and 216 deletions
+1 -1
View File
@@ -396,7 +396,7 @@
# define HAVE_STL_HASH 0
#endif
// safe string functions: strcpy_s et al.
// safe CRT functions: strcpy_s, fopen_s, etc.
// these are always available to users: if not provided by the CRT, we
// implement them ourselves. this flag is only used to skip our impl.
#if MSC_VERSION >= 1400
+108 -85
View File
@@ -34,108 +34,56 @@ AT_STARTUP(\
)
static ModuleInitState module_init_state = MODULE_BEFORE_INIT;
//-----------------------------------------------------------------------------
#pragma region Accessor functions
// prevent other modules from changing the underlying data.
// insulate caller from the system-specific modules and cache results.
// note: the providers sometimes need to store the results anyway, so we
// don't need to do caching in those cases.
// these are set once during cpu_Init since they're usually all used and
// we thus avoid needing if(already_called) return old_result.
// initially set to 'impossible' values to catch uses before cpu_Init.
bool cpu_IsModuleInitialized()
{
return module_init_state == MODULE_INITIALIZED;
}
static ModuleInitState module_init_state = MODULE_BEFORE_INIT;
static double clock_frequency = -1.0;
static bool is_throttling_possible = true;
static size_t page_size = 1;
static size_t memory_total_mib = 1;
const char* cpu_IdentifierString()
static void DetectClockFrequency()
{
#if CPU_IA32
return ia32_IdentifierString();
#endif
}
double cpu_ClockFrequency()
{
#if CPU_IA32
return ia32_ClockFrequency(); // authoritative, precise
#endif
}
uint cpu_NumPackages()
{
#if CPU_IA32
return ia32_NumPackages();
#endif
}
uint cpu_CoresPerPackage()
{
#if CPU_IA32
return ia32_CoresPerPackage();
#endif
}
uint cpu_LogicalPerCore()
{
#if CPU_IA32
return ia32_LogicalPerCore();
clock_frequency = ia32_ClockFrequency(); // authoritative, precise
#endif
}
bool cpu_IsThrottlingPossible()
static void DetectIfThrottlingPossible()
{
#if CPU_IA32
if(ia32_IsThrottlingPossible() == 1)
return true;
{
is_throttling_possible = true;
return;
}
#endif
#if OS_WIN
if(wcpu_IsThrottlingPossible() == 1)
return true;
#endif
return false;
}
#pragma endregion
//-----------------------------------------------------------------------------
// memory
static size_t cpu_page_size = 0;
// determined during cpu_Init; cleaned up and given in MiB
static size_t cpu_memory_total_mib = 0;
// System V derived (GNU/Linux, Solaris)
#if defined(_SC_AVPHYS_PAGES)
static int SysconfFromMemType(CpuMemoryIndicators mem_type)
{
switch(mem_type)
{
case CPU_MEM_TOTAL:
return _SC_PHYS_PAGES;
case CPU_MEM_AVAILABLE:
return _SC_AVPHYS_PAGES;
is_throttling_possible = true;
return;
}
UNREACHABLE;
}
#endif
size_t cpu_MemorySize(CpuMemoryIndicators mem_type)
{
// quasi-POSIX
#if defined(_SC_AVPHYS_PAGES)
const int sc_name = SysconfFromMemType(mem_type);
const size_t memory_size = sysconf(sc_name) * cpu_page_size;
return memory_size;
// BSD / Mac OS X
#else
return bsd_MemorySize(mem_type);
#endif
is_throttling_possible = false;
}
static size_t DetermineMemoryTotalMiB()
static void DetectMemory()
{
page_size = (size_t)sysconf(_SC_PAGESIZE);
size_t memory_total = cpu_MemorySize(CPU_MEM_TOTAL);
// account for inaccurate reporting by rounding up (see wposix sysconf)
@@ -147,16 +95,59 @@ static size_t DetermineMemoryTotalMiB()
else
memory_total = memory_total_pow2;
const size_t memory_total_mib = memory_total / MiB;
return memory_total_mib;
memory_total_mib = memory_total / MiB;
}
bool cpu_IsModuleInitialized()
{
return module_init_state == MODULE_INITIALIZED;
}
double cpu_ClockFrequency()
{
return clock_frequency;
}
bool cpu_IsThrottlingPossible()
{
return is_throttling_possible;
}
size_t cpu_MemoryTotalMiB()
{
return cpu_memory_total_mib;
return memory_total_mib;
}
const char* cpu_IdentifierString()
{
#if CPU_IA32
return ia32_IdentifierString(); // cached
#endif
}
uint cpu_NumPackages()
{
#if CPU_IA32
return ia32_NumPackages(); // cached
#endif
}
uint cpu_CoresPerPackage()
{
#if CPU_IA32
return ia32_CoresPerPackage(); // cached
#endif
}
uint cpu_LogicalPerCore()
{
#if CPU_IA32
return ia32_LogicalPerCore(); // cached
#endif
}
#pragma endregion
//-----------------------------------------------------------------------------
#if CPU_IA32
@@ -195,16 +186,16 @@ void cpu_Init()
InitAndConfigureIA32();
#endif
// memory
cpu_page_size = (size_t)sysconf(_SC_PAGESIZE);
cpu_memory_total_mib = DetermineMemoryTotalMiB();
DetectMemory();
DetectIfThrottlingPossible();
DetectClockFrequency();
// must be set before wtime_reset_impl since it queries this flag via
// cpu_IsModuleInitialized.
module_init_state = MODULE_INITIALIZED;
// HACK: on Windows, the HRT makes its final implementation choice
// in the first calibrate call where cpu info is available.
// in the first calibrate call where CPU info is available.
// call wtime_reset_impl here to have that happen now so app code isn't
// surprised by a timer change, although the HRT does try to
// keep the timer continuous.
@@ -215,6 +206,7 @@ void cpu_Init()
//-----------------------------------------------------------------------------
// stateless routines
bool cpu_CAS(uintptr_t* location, uintptr_t expected, uintptr_t new_value)
{
@@ -287,3 +279,34 @@ i64 cpu_i64FromDouble(double d)
return (i64)d;
#endif
}
// System V derived (GNU/Linux, Solaris)
#if defined(_SC_AVPHYS_PAGES)
static int SysconfFromMemType(CpuMemoryIndicators mem_type)
{
switch(mem_type)
{
case CPU_MEM_TOTAL:
return _SC_PHYS_PAGES;
case CPU_MEM_AVAILABLE:
return _SC_AVPHYS_PAGES;
}
UNREACHABLE;
}
#endif
size_t cpu_MemorySize(CpuMemoryIndicators mem_type)
{
// quasi-POSIX
#if defined(_SC_AVPHYS_PAGES)
const int sc_name = SysconfFromMemType(mem_type);
const size_t memory_size = sysconf(sc_name) * page_size;
return memory_size;
// BSD / Mac OS X
#else
return bsd_MemorySize(mem_type);
#endif
}
+11 -17
View File
@@ -28,26 +28,12 @@ namespace ERR
extern void cpu_Init(void);
extern bool cpu_IsModuleInitialized();
extern const char* cpu_IdentifierString();
extern double cpu_ClockFrequency();
extern bool cpu_IsThrottlingPossible();
extern uint cpu_NumPackages(); // i.e. sockets
extern uint cpu_CoresPerPackage();
extern uint cpu_LogicalPerCore();
extern bool cpu_IsThrottlingPossible();
//
// memory
//
enum CpuMemoryIndicators
{
CPU_MEM_TOTAL, CPU_MEM_AVAILABLE
};
extern size_t cpu_MemorySize(CpuMemoryIndicators mem_type);
// faster than cpu_MemorySize (caches total size determined during init),
// returns #Mebibytes (cleaned up to account e.g. for nonpaged pool)
@@ -55,7 +41,7 @@ extern size_t cpu_MemoryTotalMiB();
//
// misc
// misc (stateless)
//
// atomic "compare and swap". compare the machine word at <location> against
@@ -75,10 +61,18 @@ extern bool cpu_CAS(uintptr_t* location, uintptr_t expected, uintptr_t new_value
**/
extern void cpu_AtomicAdd(intptr_t* location, intptr_t increment);
extern void cpu_Serialize();
// enforce strong memory ordering.
extern void cpu_MemoryFence();
extern void cpu_Serialize();
enum CpuMemoryIndicators
{
CPU_MEM_TOTAL, CPU_MEM_AVAILABLE
};
extern size_t cpu_MemorySize(CpuMemoryIndicators mem_type);
// drop-in replacement for libc memcpy(). only requires CPU support for
+93 -75
View File
@@ -208,7 +208,11 @@ public:
const char* ia32_IdentifierString()
{
// 3 calls x 4 registers x 4 bytes = 48
static char identifier_string[48+1] = "";
static char identifier_string[48+1] = {'\0'};
// not first call, return previous result
if(identifier_string[0] != '\0')
return identifier_string;
// get processor signature
u32 regs[4];
@@ -309,92 +313,106 @@ int ia32_IsThrottlingPossible()
}
// set scheduling priority and restore when going out of scope.
class ScopedSetPriority
{
int m_old_policy;
sched_param m_old_param;
public:
ScopedSetPriority(int new_priority)
{
// get current scheduling policy and priority
pthread_getschedparam(pthread_self(), &m_old_policy, &m_old_param);
// set new priority
sched_param new_param = {0};
new_param.sched_priority = new_priority;
pthread_setschedparam(pthread_self(), SCHED_FIFO, &new_param);
}
~ScopedSetPriority()
{
// restore previous policy and priority.
pthread_setschedparam(pthread_self(), m_old_policy, &m_old_param);
}
};
double ia32_ClockFrequency()
{
double clock_frequency = 0.0;
// if the TSC isn't available, there's really no good way to count the
// actual CPU clocks per known time interval, so bail.
// note: loop iterations ("bogomips") are not a reliable measure due
// to differing IPC and compiler optimizations.
if(!ia32_cap(IA32_CAP_TSC))
return -1.0; // impossible value
// set max priority, to reduce interference while measuring.
int old_policy; static sched_param old_param; // (static => 0-init)
pthread_getschedparam(pthread_self(), &old_policy, &old_param);
static sched_param max_param;
max_param.sched_priority = sched_get_priority_max(SCHED_FIFO);
pthread_setschedparam(pthread_self(), SCHED_FIFO, &max_param);
// increase priority to reduce interference while measuring.
const int priority = sched_get_priority_max(SCHED_FIFO)-1;
ScopedSetPriority ssp(priority);
// make sure the TSC is available, because we're going to
// measure actual CPU clocks per known time interval.
// counting loop iterations ("bogomips") is unreliable.
if(ia32_cap(IA32_CAP_TSC))
// note: no need to "warm up" cpuid - it will already have been
// called several times by the time this code is reached.
// (background: it's used in ia32_rdtsc() to serialize instruction flow;
// the first call is documented to be slower on Intel CPUs)
int num_samples = 16;
// if clock is low-res, do less samples so it doesn't take too long.
// balance measuring time (~ 10 ms) and accuracy (< 1 0/00 error -
// ok for using the TSC as a time reference)
if(timer_res() >= 1e-3)
num_samples = 8;
std::vector<double> samples(num_samples);
for(int i = 0; i < num_samples; i++)
{
// note: no need to "warm up" cpuid - it will already have been
// called several times by the time this code is reached.
// (background: it's used in ia32_rdtsc() to serialize instruction flow;
// the first call is documented to be slower on Intel CPUs)
double dt;
i64 dc; // i64 because VC6 can't convert u64 -> double,
// and we don't need all 64 bits.
int num_samples = 16;
// if clock is low-res, do less samples so it doesn't take too long.
// balance measuring time (~ 10 ms) and accuracy (< 1 0/00 error -
// ok for using the TSC as a time reference)
if(timer_res() >= 1e-3)
num_samples = 8;
std::vector<double> samples(num_samples);
int i;
for(i = 0; i < num_samples; i++)
// count # of clocks in max{1 tick, 1 ms}:
// .. wait for start of tick.
const double t0 = get_time();
u64 c1; double t1;
do
{
double dt;
i64 dc;
// i64 because VC6 can't convert u64 -> double,
// and we don't need all 64 bits.
// count # of clocks in max{1 tick, 1 ms}:
// .. wait for start of tick.
const double t0 = get_time();
u64 c1; double t1;
do
{
// note: get_time effectively has a long delay (up to 5 us)
// before returning the time. we call it before ia32_rdtsc to
// minimize the delay between actually sampling time / TSC,
// thus decreasing the chance for interference.
// (if unavoidable background activity, e.g. interrupts,
// delays the second reading, inaccuracy is introduced).
t1 = get_time();
c1 = ia32_rdtsc();
}
while(t1 == t0);
// .. wait until start of next tick and at least 1 ms elapsed.
do
{
const double t2 = get_time();
const u64 c2 = ia32_rdtsc();
dc = (i64)(c2 - c1);
dt = t2 - t1;
}
while(dt < 1e-3);
// .. freq = (delta_clocks) / (delta_seconds);
// ia32_rdtsc/timer overhead is negligible.
const double freq = dc / dt;
samples[i] = freq;
// note: get_time effectively has a long delay (up to 5 us)
// before returning the time. we call it before ia32_rdtsc to
// minimize the delay between actually sampling time / TSC,
// thus decreasing the chance for interference.
// (if unavoidable background activity, e.g. interrupts,
// delays the second reading, inaccuracy is introduced).
t1 = get_time();
c1 = ia32_rdtsc();
}
while(t1 == t0);
// .. wait until start of next tick and at least 1 ms elapsed.
do
{
const double t2 = get_time();
const u64 c2 = ia32_rdtsc();
dc = (i64)(c2 - c1);
dt = t2 - t1;
}
while(dt < 1e-3);
std::sort(samples.begin(), samples.end());
// median filter (remove upper and lower 25% and average the rest).
// note: don't just take the lowest value! it could conceivably be
// too low, if background processing delays reading c1 (see above).
double sum = 0.0;
const int lo = num_samples/4, hi = 3*num_samples/4;
for(i = lo; i < hi; i++)
sum += samples[i];
clock_frequency = sum / (hi-lo);
// .. freq = (delta_clocks) / (delta_seconds);
// ia32_rdtsc/timer overhead is negligible.
const double freq = dc / dt;
samples[i] = freq;
}
// else: TSC not available, can't measure; cpu_freq remains unchanged.
// restore previous policy and priority.
pthread_setschedparam(pthread_self(), old_policy, &old_param);
std::sort(samples.begin(), samples.end());
// median filter (remove upper and lower 25% and average the rest).
// note: don't just take the lowest value! it could conceivably be
// too low, if background processing delays reading c1 (see above).
double sum = 0.0;
const int lo = num_samples/4, hi = 3*num_samples/4;
for(int i = lo; i < hi; i++)
sum += samples[i];
const double clock_frequency = sum / (hi-lo);
return clock_frequency;
}
+93 -36
View File
@@ -65,7 +65,7 @@ AT_STARTUP(\
// (default values for HRT_NONE impl)
// initial measurement of the time source's tick rate. not necessarily
// correct (e.g. when using TSC; cpu_ClockFrequency isn't exact).
// correct (e.g. when using TSC: cpu_ClockFrequency isn't exact).
static double hrt_nominal_freq = -1.0;
// actual resolution of the time source (may differ from hrt_nominal_freq
@@ -130,9 +130,9 @@ enum HRTOverride
HRT_FORCE
};
// HRTImpl enums as index
// HACK: no init needed - static data is zeroed (= HRT_DEFAULT)
static HRTOverride overrides[HRT_NUM_IMPLS];
// HRTImpl enums as index
// HACK: no init needed - static data is zeroed (= HRT_DEFAULT)
cassert((int)HRT_DEFAULT == 0);
@@ -152,6 +152,14 @@ static inline void unlock(void)
}
static bool IsSimilarMagnitude(double d1, double d2, const double relative_error_tolerance = 0.05)
{
const double relative_error = fabs(d1/d2 - 1.0);
if(relative_error > relative_error_tolerance)
return false;
return true;
}
// decide upon a HRT implementation, checking if we can work around
// each timer's issues on this platform, but allow user override
// in case there are unforeseen problems with one of them.
@@ -166,6 +174,9 @@ static LibError choose_impl()
if(overrides[impl] == HRT_FORCE)\
safe = true;
// used several times below, so latch it for convenience.
const double cpu_freq = cpu_IsModuleInitialized()? cpu_ClockFrequency() : 0.0;
#if CPU_IA32 && !defined(NO_TSC)
// CPU Timestamp Counter (incremented every clock)
// ns resolution, moderate precision (poor clock crystal?)
@@ -187,7 +198,62 @@ static LibError choose_impl()
// will do this as well (if not to save power, for heat reasons).
// frequency changes are too often and drastic to correct,
// and we don't want to mess with the system power settings => unsafe.
if(cpu_IsModuleInitialized() && cpu_ClockFrequency() > 0.0 && ia32_cap(IA32_CAP_TSC))
/*
AMD has defined a CPUID feature bit that
software can test to determine if the TSC is
invariant. Issuing a CPUID instruction with an %eax register
value of 0x8000_0007, on a processor whose base family is
0xF, returns "Advanced Power Management Information" in the
%eax, %ebx, %ecx, and %edx registers. Bit 8 of the return
%edx is the "TscInvariant" feature flag which is set when
TSC is P-state, C-state, and STPCLK-throttling invariant; it
is clear otherwise.
*/
/*
if (CPUID.base_family < 0xf) {
// TSC drift doesn't exist on 7th Gen or less
// However, OS still needs to consider effects
// of P-state changes on TSC
return TRUE;
} else if (CPUID.AdvPowerMgmtInfo.TscInvariant) {
// Invariant TSC on 8th Gen or newer, use it
// (assume all cores have invariant TSC)
return TRUE;
} else if ((number_processors == 1)&&(number_cores == 1)){
// OK to use TSC on uni-processor-uni-core
// However, OS still needs to consider effects
// of P-state changes on TSC
return TRUE;
} else if ( (number_processors == 1) &&
(CPUID.effective_family == 0x0f) &&
!C1_ramp_8gen ){
// Use TSC on 8th Gen uni-proc with C1_ramp off
// However, OS still needs to consider effects
// of P-state changes on TSC
return TRUE;
} else {
return FALSE;
}
}
C1_ramp_8gen() {
// Check if C1-Clock ramping enabled in PMM7.CpuLowPwrEnh
// On 8th-Generation cores only. Assume BIOS has setup
// all Northbridges equivalently.
return (1 & read_pci_byte(bus=0,dev=0x18,fcn=3,offset=0x87));
}
*/
if(cpu_freq > 0.0 && ia32_cap(IA32_CAP_TSC))
{
safe = (cpu_CoresPerPackage() == 1 && cpu_NumPackages() == 1 && cpu_IsThrottlingPossible() == 0);
SAFETY_OVERRIDE(HRT_TSC);
@@ -213,17 +279,15 @@ static LibError choose_impl()
// 2) "System clock problem can inflate benchmark scores":
// incorrect value if not polled every 4.5 seconds? solved
// by calibration thread, which reads timer every second anyway.
// - TSC on MP HAL - see TSC above.
// - TSC on MP HAL, sometimes with 1/3 of CPU freq.
// cache freq because QPF is fairly slow.
static i64 qpc_freq = -1;
// first call - check if QPC is supported
if(qpc_freq == -1)
static i64 qpc_freq = -1; // set to 0 if unsupported
if(qpc_freq == -1) // first call
{
LARGE_INTEGER i;
BOOL qpc_ok = QueryPerformanceFrequency(&i);
qpc_freq = qpc_ok? i.QuadPart : 0;
LARGE_INTEGER freq;
BOOL qpc_ok = QueryPerformanceFrequency(&freq);
qpc_freq = qpc_ok? freq.QuadPart : 0;
}
// QPC is available
@@ -241,11 +305,14 @@ static LibError choose_impl()
safe = false;
else
{
// compare QPC freq to CPU clock freq - can't rule out HPET,
// because its frequency isn't known (it's at least 10 MHz).
double freq_dist = fabs(cpu_ClockFrequency()/qpc_freq - 1.0);
safe = freq_dist > 0.05;
// safe if freqs not within 5% (i.e. it doesn't use TSC)
safe = true;
// compare QPC freq to CPU clock freq. note: we can't
// single out the HPET (as with PIT and PMT above) because
// its frequency is variable and at least 10 MHz.
if(IsSimilarMagnitude(qpc_freq, cpu_freq))
safe = false;
if(IsSimilarMagnitude(qpc_freq, cpu_freq/3)) // QPC sometimes uses RDTSC/3
safe = false;
}
}
@@ -318,12 +385,8 @@ static i64 ticks_lk()
// add further timers here.
case HRT_NUM_IMPLS:
default:
debug_warn("invalid impl");
//-fallthrough
case HRT_NONE:
return 0;
} // switch(impl)
}
@@ -348,7 +411,7 @@ static double time_lk()
// this module is dependent upon detect (supplies system information needed to
// this module is dependent upon cpu.cpp (supplies information needed to
// choose a HRT), which in turn uses our timer to detect the CPU clock
// when running on Windows (clock(), the only cross platform HRT available on
// Windows, isn't good enough - only 10..15 ms resolution).
@@ -390,6 +453,8 @@ static LibError reset_impl_lk()
hrt_cal_ticks = ticks_lk();
}
debug_printf("HRT impl=%d nominal_freq=%f cur_freq=%f\n", hrt_impl, hrt_nominal_freq, hrt_cur_freq);
return INFO::OK;
}
@@ -408,9 +473,8 @@ unlock();
// return seconds since init.
static double hrt_time()
{
double t;
lock();
t = time_lk();
const double t = time_lk();
unlock();
return t;
}
@@ -423,7 +487,7 @@ static double hrt_delta_s(i64 start, i64 end)
{
// paranoia: reading double may not be atomic.
lock();
double freq = hrt_cur_freq;
const double freq = hrt_cur_freq;
unlock();
debug_assert(freq != -1.0 && "hrt_delta_s: hrt_cur_freq not set");
@@ -434,8 +498,6 @@ unlock();
// return current timer implementation and its nominal (rated) frequency.
// nominal_freq is never 0.
// implementation only changes after hrt_override_impl.
//
// may be called before first hrt_ticks / hrt_time, so do init here also.
static void hrt_query_impl(HRTImpl& impl, double& nominal_freq, double& res)
{
lock();
@@ -472,12 +534,9 @@ unlock();
}
//////////////////////////////////////////////////////////////////////////////
//
//-----------------------------------------------------------------------------
// calibration
//
//////////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------------
// 'safe' timer, used to measure HRT freq in calibrate()
static const long safe_timer_freq = 1000;
@@ -629,11 +688,9 @@ static LibError hrt_shutdown()
}
//////////////////////////////////////////////////////////////////////////////
//
//-----------------------------------------------------------------------------
// wtime wrapper: emulates POSIX functions
//
//////////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------------
// NT system time and FILETIME are hectonanoseconds since Jan. 1, 1601 UTC.
// SYSTEMTIME is a struct containing month, year, etc.
+1 -1
View File
@@ -129,7 +129,7 @@ static char ds_drv_path[MAX_PATH+1];
// store sound card name and path to DirectSound driver.
// called for each DirectSound driver, but aborts after first valid driver.
static BOOL CALLBACK ds_enum(void* UNUSED(guid), const char* description,
const char* module, void* UNUSED(ctx))
const char* module, void* UNUSED(ctx))
{
// skip first (dummy) entry, where description == "Primary Sound Driver".
if(module[0] == '\0')
+1 -1
View File
@@ -58,7 +58,7 @@ double get_time()
// make sure time is monotonic (never goes backwards)
static double t_last = 0.0;
if(t < t_last)
t = t_last;
t = t_last+DBL_EPSILON;
t_last = t;
return t;