need help w/ multi-threaded, multi-CPU tick count / stopwatch

N

not_a_commie

So I have a motherboard with multiple CPU sockets. It seems that if I
create a StopWatch on one thread and then call the Elapsed member from
a different thread that sometimes I get a tick count that's a million
miles away.

My thinking is that I can subclass the StopWatch. Then when the
Elapsed member is called, I can invoke it on the thread that the
StopWatch class was created on. True? How?

Or is there an easy way that I could scan through all processors and
read their tick counts? That way I could just have my own timer class
that stores counts for all CPUs.

Can the BeginThreadAffinity help me somehow? I'm totally at a loss as
to what that function does for me.

Is there some other solution?

I'm using .NET 2.0.

Thanks.
 
B

Brian Gideon

So I have a motherboard with multiple CPU sockets. It seems that if I
create a StopWatch on one thread and then call the Elapsed member from
a different thread that sometimes I get a tick count that's a million
miles away.

My thinking is that I can subclass the StopWatch. Then when the
Elapsed member is called, I can invoke it on the thread that the
StopWatch class was created on. True? How?

Or is there an easy way that I could scan through all processors and
read their tick counts? That way I could just have my own timer class
that stores counts for all CPUs.

Can the BeginThreadAffinity help me somehow? I'm totally at a loss as
to what that function does for me.

Is there some other solution?

I'm using .NET 2.0.

Thanks.

Hi,

The StopWatch class isn't inherently thread-safe. Are you
synchronizing access to it appropriately. Can you post some code
demonstrating the problem?

Brian
 
W

Willy Denoyette [MVP]

not_a_commie said:
So I have a motherboard with multiple CPU sockets. It seems that if I
create a StopWatch on one thread and then call the Elapsed member from
a different thread that sometimes I get a tick count that's a million
miles away.

My thinking is that I can subclass the StopWatch. Then when the
Elapsed member is called, I can invoke it on the thread that the
StopWatch class was created on. True? How?

Or is there an easy way that I could scan through all processors and
read their tick counts? That way I could just have my own timer class
that stores counts for all CPUs.

Can the BeginThreadAffinity help me somehow? I'm totally at a loss as
to what that function does for me.

Is there some other solution?

I'm using .NET 2.0.

Thanks.

Check this: http://support.microsoft.com/?id=896256

Willy.
 
N

not_a_commie

The StopWatch class isn't inherently thread-safe. Are you
synchronizing access to it appropriately. Can you post some code
demonstrating the problem?

Right. The StopWatch class expects that the Reset/Start/Stop/Elapsed*
methods are all accessed from the same thread. Not only that, they all
have to be accessed from the same CPU. Does StopWatch use the
BeginThreadAffinity internally to make this happen? I'll assume that
it does. The problem is that if you need to call Reset in one thread
and Elapsed in another, this is very difficult. I did manage to make a
class to do it, however. Here it is for your critique:

using System;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Threading;

namespace Blah
{
/// <summary>
/// Represents a high-resolution stopwatch that is thread-safe and
CPU safe.
/// </summary>
public sealed class StopWatch
{
static private Thread _backgroundThread = null;
static private EventWaitHandle _tickCountUpdateRequest = new
EventWaitHandle(false, EventResetMode.AutoReset);
static private EventWaitHandle _tickCountUpdateDone = new
EventWaitHandle(false, EventResetMode.AutoReset);

static public long Frequency = 0;
static public long CurrentTickCount = 0;

public long StartTickCount = 0;

/// <summary>
/// A thread to make sure CPU tick counts are always read from the
same CPU
/// </summary>
private static void _backgroundThreadFunc()
{
Thread.BeginThreadAffinity();
Frequency = System.Diagnostics.Stopwatch.Frequency;
while (true)
{
_tickCountUpdateRequest.WaitOne();
CurrentTickCount = System.Diagnostics.Stopwatch.GetTimestamp();
_tickCountUpdateDone.Set();
}
//Thread.EndThreadAffinity(); // never called
}

/// <summary>
/// The System.Diagnostics.Stopwatch (and hence, performance
counters) get their tick marks from the CPU that the thread is on. We
create a thread to make sure the tick counts are always read on the
same CPU.
/// </summary>
static StopWatch() {
_backgroundThread = new Thread(new
ThreadStart(_backgroundThreadFunc));
_backgroundThread.IsBackground = true;
_backgroundThread.Start();
}

/// <summary>
/// Initializes a new instance of the StopWatch class.
/// </summary>
/// <exception cref="NotSupportedException">The system does not have
a high-resolution performance counter.</exception>
public StopWatch()
{
Reset();
}

/// <summary>
/// Resets the stopwatch. This method should be called when you
start measuring.
/// </summary>
/// <exception cref="NotSupportedException">The system does not have
a high-resolution performance counter.</exception>
public void Reset()
{
lock (_backgroundThread)
{
StopWatch._tickCountUpdateRequest.Set();
StopWatch._tickCountUpdateDone.WaitOne();
StartTickCount = CurrentTickCount;
}
}

/// <summary>
/// Peg the processor for this many seconds; mainly used for testing
/// </summary>
/// <param name="seconds"></param>
public static void BusyLoop(double seconds)
{
DateTime endTime = DateTime.Now.AddSeconds(seconds);
int i = 0;
while (DateTime.Now < endTime) { i++; }
}

public long GetCurrentTime_ms()
{
lock (_backgroundThread)
{
StopWatch._tickCountUpdateRequest.Set();
StopWatch._tickCountUpdateDone.WaitOne();
return CurrentTickCount * 1000 / StopWatch.Frequency;
}
}

/// <summary>
/// Get the time elapsed, in seconds, since the last Reset() or
since
/// creation if Reset() hasn't been called since then.
/// </summary>
/// <returns>Elapsed time in seconds.</returns>
public double GetElapsed_s()
{
lock (_backgroundThread)
{
StopWatch._tickCountUpdateRequest.Set();
StopWatch._tickCountUpdateDone.WaitOne();
return (double)(CurrentTickCount - StartTickCount) /
(double)StopWatch.Frequency;
}
}

/// <summary>
/// Get the time elapsed, in milliseconds, since the last Reset() or
since
/// creation if Reset() hasn't been called since then.
/// </summary>
/// <returns>Elapsed time in milliseconds.</returns>
public long GetElapsed_ms()
{
lock (_backgroundThread)
{
StopWatch._tickCountUpdateRequest.Set();
StopWatch._tickCountUpdateDone.WaitOne();
return (CurrentTickCount - StartTickCount) * 1000 /
StopWatch.Frequency;
}
}
}
}
 
W

Willy Denoyette [MVP]

not_a_commie said:
Right. The StopWatch class expects that the Reset/Start/Stop/Elapsed*
methods are all accessed from the same thread. Not only that, they all
have to be accessed from the same CPU. Does StopWatch use the
BeginThreadAffinity internally to make this happen? I'll assume that
it does. The problem is that if you need to call Reset in one thread
and Elapsed in another, this is very difficult. I did manage to make a
class to do it, however. Here it is for your critique:

using System;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Threading;

namespace Blah
{
/// <summary>
/// Represents a high-resolution stopwatch that is thread-safe and
CPU safe.
/// </summary>
public sealed class StopWatch
{
static private Thread _backgroundThread = null;
static private EventWaitHandle _tickCountUpdateRequest = new
EventWaitHandle(false, EventResetMode.AutoReset);
static private EventWaitHandle _tickCountUpdateDone = new
EventWaitHandle(false, EventResetMode.AutoReset);

static public long Frequency = 0;
static public long CurrentTickCount = 0;

public long StartTickCount = 0;

/// <summary>
/// A thread to make sure CPU tick counts are always read from the
same CPU
/// </summary>
private static void _backgroundThreadFunc()
{
Thread.BeginThreadAffinity();
Frequency = System.Diagnostics.Stopwatch.Frequency;
while (true)
{
_tickCountUpdateRequest.WaitOne();
CurrentTickCount = System.Diagnostics.Stopwatch.GetTimestamp();
_tickCountUpdateDone.Set();
}
//Thread.EndThreadAffinity(); // never called
}

/// <summary>
/// The System.Diagnostics.Stopwatch (and hence, performance
counters) get their tick marks from the CPU that the thread is on. We
create a thread to make sure the tick counts are always read on the
same CPU.
/// </summary>
static StopWatch() {
_backgroundThread = new Thread(new
ThreadStart(_backgroundThreadFunc));
_backgroundThread.IsBackground = true;
_backgroundThread.Start();
}

/// <summary>
/// Initializes a new instance of the StopWatch class.
/// </summary>
/// <exception cref="NotSupportedException">The system does not have
a high-resolution performance counter.</exception>
public StopWatch()
{
Reset();
}

/// <summary>
/// Resets the stopwatch. This method should be called when you
start measuring.
/// </summary>
/// <exception cref="NotSupportedException">The system does not have
a high-resolution performance counter.</exception>
public void Reset()
{
lock (_backgroundThread)
{
StopWatch._tickCountUpdateRequest.Set();
StopWatch._tickCountUpdateDone.WaitOne();
StartTickCount = CurrentTickCount;
}
}

/// <summary>
/// Peg the processor for this many seconds; mainly used for testing
/// </summary>
/// <param name="seconds"></param>
public static void BusyLoop(double seconds)
{
DateTime endTime = DateTime.Now.AddSeconds(seconds);
int i = 0;
while (DateTime.Now < endTime) { i++; }
}

public long GetCurrentTime_ms()
{
lock (_backgroundThread)
{
StopWatch._tickCountUpdateRequest.Set();
StopWatch._tickCountUpdateDone.WaitOne();
return CurrentTickCount * 1000 / StopWatch.Frequency;
}
}

/// <summary>
/// Get the time elapsed, in seconds, since the last Reset() or
since
/// creation if Reset() hasn't been called since then.
/// </summary>
/// <returns>Elapsed time in seconds.</returns>
public double GetElapsed_s()
{
lock (_backgroundThread)
{
StopWatch._tickCountUpdateRequest.Set();
StopWatch._tickCountUpdateDone.WaitOne();
return (double)(CurrentTickCount - StartTickCount) /
(double)StopWatch.Frequency;
}
}

/// <summary>
/// Get the time elapsed, in milliseconds, since the last Reset() or
since
/// creation if Reset() hasn't been called since then.
/// </summary>
/// <returns>Elapsed time in milliseconds.</returns>
public long GetElapsed_ms()
{
lock (_backgroundThread)
{
StopWatch._tickCountUpdateRequest.Set();
StopWatch._tickCountUpdateDone.WaitOne();
return (CurrentTickCount - StartTickCount) * 1000 /
StopWatch.Frequency;
}
}
}
}


Please read my other reply, your issue is probably related to a failure of the SMP HAL to
synchronize the CPU clocks when running this on anything else than Vista or Longorn.

Willy.
 
B

Brian Gideon

Right. The StopWatch class expects that the Reset/Start/Stop/Elapsed*
methods are all accessed from the same thread. Not only that, they all
have to be accessed from the same CPU. Does StopWatch use the
BeginThreadAffinity internally to make this happen? I'll assume that
it does. The problem is that if you need to call Reset in one thread
and Elapsed in another, this is very difficult. I did manage to make a
class to do it, however. Here it is for your critique:

Ah yes, there are processor affinity problems. I didn't notice this
note the first time I read through the documentation.

"On a multiprocessor computer, it does not matter which processor the
thread runs on. However, because of bugs in the BIOS or the Hardware
Abstraction Layer (HAL), you can get different timing results on
different processors. To specify processor affinity for a thread, use
the ProcessThread.ProcessorAffinity method."

I didn't see anything about thread affinity though so it should work
when called from different threads right?

Brian
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top