Solved

Using QueryPerformanceCounter on a multi-core system

Posted on 2011-09-12
7
1,197 Views
Last Modified: 2012-05-12
I need to use the Win API QueryPerformanceCounter call in my VB.NET 2008 application.  The documentation I can find warns that the call result can be unreliable on multicore systems unless SetThreadAffinityMask is called to force the QueryPerformanceCounter call to always run on the same core.  I'm having trouble figuring out exactly how to use GetProcessAffinityMask and SetThreadAffinityMask to accomplish this.  

Can anybody offer any known-good code examples?
0
Comment
Question by:RHenningsgard
  • 4
  • 3
7 Comments
 
LVL 96

Expert Comment

by:Bob Learned
ID: 36528858
I am wondering what special functionality that you need that you can't get from the CLR performance counter stuff?

Getting Started With the Performance Counters
http://www.getdotnetcode.com/gdncstore/free/Articles/Getting%20Started%20With%20Performance%20Counters.htm
0
 
LVL 2

Author Comment

by:RHenningsgard
ID: 36529407
Thanks for the interesting link, but I didn't find anything relevant to my issue there.

I am using Kernel32.DLL's QueryPerformanceFrequency() call to get the frequency at which the counter updates, and QueryPerformanceCounter() to get the time count in ticks at the frequency reported by QueryPerformanceFrequency().  The application is a precision measurement system which continuously logs a device's outputs, and the current time.  It's kind of like a "flight data recorder" operation.  So my need is to repeatedly get the time, at the highest accuracy and precision possible, so my log results won't jitter due to errors in the reported time.

If it were not for the multicore OS environment and the stern warnings of Microsoft, I'd be using assembly and the RDTSC "Read Time Stamp Counter" instruction to just get the accumulated CPU ticks and compute the time from that.  The warnings are that in a multicore system, you can never be certain which CPU's count you're receiving, and therefore it's possible to get wild swings in the reported "time."

A similar warning appears in several places in the MSDN documentation about the stability of the QueryPerformanceCounter output, and the recommendation is the use of GetProcessAffinityMask and then SetThreadAffinityMask to force the calls to always occur in the same core.  That's why I'm looking for known-good sample code, as I've had difficulty figuring out how to code the VB.NET to get those calls to work.
0
 
LVL 96

Expert Comment

by:Bob Learned
ID: 36529418
I was asking why you are using Win32 API, instead of the managed performance counter?
0
Better Security Awareness With Threat Intelligence

See how one of the leading financial services organizations uses Recorded Future as part of a holistic threat intelligence program to promote security awareness and proactively and efficiently identify threats.

 
LVL 2

Author Comment

by:RHenningsgard
ID: 36529532
I have been unable to figure out how to get the managed performance counter to just provide me the system time in high-resolution ticks, the way the unmanaged QueryPerformanceCounter() does.  It seems that all of the calls I can find in the managed PerformanceCounter class pertain to measuring OS and process performance, not just giving me the raw time.
0
 
LVL 96

Accepted Solution

by:
Bob Learned earned 500 total points
ID: 36530270
That is good information to know.  

I would take a look here:

Spike Your CPU’s Processor in .Net
http://omegacoder.com/?p=94

Calculate PI
http://omegacoder.com/?p=91
using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;

// Target a specific processor for the thread to run on
public class ThreadProcessor
{
    
    [DllImport("kernel32.dll")]
    private static extern IntPtr GetCurrentThread();

    [DllImport("kernel32.dll")]
    private static extern IntPtr SetThreadAffinityMask(IntPtr hThread, IntPtr dwThreadAffinityMask);

    public static void Usage()
    {
        int cpu = 0;
        ThreadProcessor tp = new ThreadProcessor();
        Console.WriteLine("Spike CPU 1");
        tp.SpikeCPU(cpu);
        if (tp._ex != null)
        {
            Console.WriteLine(tp._ex.Message);
        }
        else
        {
            if (Environment.ProcessorCount > 1)
            {
                while (++cpu < Environment.ProcessorCount)
                {
                    Thread.Sleep(1000);
                    Console.WriteLine("Spike CPU " + (cpu + 1).ToString());
                    tp.SpikeCPU(cpu);
                    if (tp._ex != null)
                    {
                        Console.WriteLine(tp._ex.Message);
                        break;
                    }
                }
            }
            else // Either a single CPU or hyperthreading not enabled in the OS or the BIOS. 
            {
                Console.WriteLine("This PC does not have two processors available.");
            }
        }
    }
    private Thread _worker;
    private const int PiSignificantDigits = 750; // Adjust to your processor     
    // Spike the CPU and waith for it to finish    
    public void SpikeCPU(int targetCPU)
    {
        // Create a worker thread for the work.        
        _worker = new Thread(DoBusyWork);         // Background is set so not to not prevent the        
        // mainprocess from terminating if someone closes it.        
        _worker.IsBackground = true;
        _worker.Start((object) targetCPU);
        _worker.Join(); // Wait for it to be done.    
    }

    public void DoBusyWork(object target)
    {
        try
        {
            int processor = (int) target;
            Thread tr = Thread.CurrentThread;
            if (Environment.ProcessorCount > 1)
            {
                SetThreadAffinityMask(GetCurrentThread(), new IntPtr(1 << processor));
            }
            CalculatePI.Process(PiSignificantDigits);
        }
        catch (Exception ex)
        {
            _ex = ex;
        }
    }
    public Exception _ex = null;
}

// Source:
// http://omegacoder.com/?p=91

public class CalculatePI
{
    /*      
     Computation of the n'th decimal digit of \pi with very little memory.      
     Written by Fabrice Bellard on January 8, 1997.      
     We use a slightly modified version of the method described by Simon      
     Plouffe in "On the Computation of the n'th decimal digit of various
     transcendental numbers" (November 1996). We have modified the algorithm
     to get a running time of O(n^2) instead of O(n^3log(n)^3).     
     
     This program uses mostly integer arithmetic. It may be slow on some 
     hardware where integer multiplications and divisions must be done
     by software. We have supposed that 'int' has a size of 32 bits. If 
     your compiler supports 'long long' integers of 64 bits, you may use
     the integer version of 'mul_mod' (see HAS_LONG_LONG).      
     
     Call this static to use. 
     */

    public static string Process(int digits)
    {
        StringBuilder result = new StringBuilder();
        result.Append("3.");
        DateTime StartTime = DateTime.Now;
        if (digits > 0)
        {
            for (int i = 0; i < digits; i += 9)
            {
                String ds = CalculatePiDigits(i + 1);
                int digitCount = Math.Min(digits - i, 9);
                if (ds.Length < 9)
                    ds = string.Format("{0:D9}", int.Parse(ds));
                result.Append(ds.Substring(0, digitCount));
            }
        }
        return result.ToString();
    }
    private static int mul_mod(int a, int b, int m)
    {
        return (int) (((long) a * (long) b) % m);
    }
    /* return the inverse of x mod y */
    private static int inv_mod(int x, int y)
    {
        int q, u, v, a, c, t;
        u = x;
        v = y;
        c = 1;
        a = 0;
        do
        {
            q = v / u;
            t = c;
            c = a - q * c;
            a = t;
            t = u;
            u = v - q * u;
            v = t;
        } while (u != 0);

        a = a % y;
        if (a < 0)
        {
            a = y + a;
        }
        return a;
    }
    /* return (a^b) mod m */

    private static int pow_mod(int a, int b, int m)
    {
        int r, aa;
        r = 1;
        aa = a;
        while (true)
        {
            if ((b & 1) != 0)
            {
                r = mul_mod(r, aa, m);
            }

            b = b >> 1;

            if (b == 0)
            {
                break;
            }

            aa = mul_mod(aa, aa, m);
        }
        return r;
    }

    /* return true if n is prime */
    private static bool is_prime(int n)
    {
        if ((n % 2) == 0)
        {
            return false;
        }

        int r = (int) Math.Sqrt(n);

        for (int i = 3; i <= r; i += 2)
        {
            if ((n % i) == 0)
            {
                return false;
            }
        }
        return true;
    }

    /* return the prime number immediatly after n */
    private static int next_prime(int n)
    {
        do
        {
            n++;
        } while (!is_prime(n));
        return n;
    }

    private static String CalculatePiDigits(int n)
    {
        int av, vmax, num, den, s, t;
        int N = (int) ((n + 20) * Math.Log(10) / Math.Log(2));
        double sum = 0;
        for (int a = 3; a <= (2 * N); a = next_prime(a))
        {
            vmax = (int) (Math.Log(2 * N) / Math.Log(a));
            av = 1;
            for (int i = 0; i < vmax; i++)
            {
                av = av * a;
            }
            s = 0;
            num = 1;
            den = 1;
            int v = 0;
            int kq = 1;
            int kq2 = 1;

            for (int k = 1; k <= N; k++)
            {
                t = k;
                if (kq >= a)
                {
                    do
                    {
                        t = t / a;
                        v--;
                    } while ((t % a) == 0);

                    kq = 0;
                }
                kq++;
                num = mul_mod(num, t, av);
                t = 2 * k - 1;
                if (kq2 >= a)
                {
                    if (kq2 == a)
                    {
                        do
                        {
                            t = t / a;
                            v++;
                        } while ((t % a) == 0);
                    }

                    kq2 -= a;
                }
                den = mul_mod(den, t, av);
                kq2 += 2;
                if (v > 0)
                {
                    t = inv_mod(den, av);
                    t = mul_mod(t, num, av);
                    t = mul_mod(t, k, av);
                    for (int i = v; i < vmax; i++)
                    {
                        t = mul_mod(t, a, av);
                    }
                    s += t;
                    if (s >= av)
                    {
                        s -= av;
                    }
                }
            }

            t = pow_mod(10, n - 1, av);
            s = mul_mod(s, t, av);
            sum = (sum + (double) s / (double) av) % 1.0;
        }
        int Result = (int) (sum * 1e9);

        String StringResult = String.Format("{0:D9}", Result);
        return StringResult;
    }

    // Put a space between every group of 10 digits.     
    private static String breakDigitsIntoGroupsOf10(String digits)
    {
        String result = "";
        while (digits.Length > 10)
        {
            result += digits.Substring(0, 10) + " ";
            digits = digits.Substring(10, digits.Length - 10);
        }
        result += digits;
        return result;
    }
}

Open in new window

Imports System.Runtime.InteropServices
Imports System.Text
Imports System.Threading

' Target a specific processor for the thread to run on
Public Class ThreadProcessor

    <DllImport("kernel32.dll")> _
    Private Shared Function GetCurrentThread() As IntPtr
    End Function

    <DllImport("kernel32.dll")> _
    Private Shared Function SetThreadAffinityMask(hThread As IntPtr, dwThreadAffinityMask As IntPtr) As IntPtr
    End Function

    Public Shared Sub Usage()
        Dim cpu As Integer = 0
        Dim tp As New ThreadProcessor()
        Console.WriteLine("Spike CPU 1")
        tp.SpikeCPU(cpu)
        If tp._ex IsNot Nothing Then
            Console.WriteLine(tp._ex.Message)
        Else
            If Environment.ProcessorCount > 1 Then
                While System.Threading.Interlocked.Increment(cpu) < Environment.ProcessorCount
                    Thread.Sleep(1000)
                    Console.WriteLine("Spike CPU " & (cpu + 1).ToString())
                    tp.SpikeCPU(cpu)
                    If tp._ex IsNot Nothing Then
                        Console.WriteLine(tp._ex.Message)
                        Exit While
                    End If
                End While
            Else
                ' Either a single CPU or hyperthreading not enabled in the OS or the BIOS. 
                Console.WriteLine("This PC does not have two processors available.")
            End If
        End If
    End Sub

    Private _worker As Thread
    Private Const PiSignificantDigits As Integer = 750

    ' Adjust to your processor     
    ' Spike the CPU and waith for it to finish    
    Public Sub SpikeCPU(targetCPU As Integer)
        ' Create a worker thread for the work.        
        _worker = New Thread(AddressOf DoBusyWork)
        ' Background is set so not to not prevent the        
        ' mainprocess from terminating if someone closes it.        
        _worker.IsBackground = True
        _worker.Start(DirectCast(targetCPU, Object))
        _worker.Join()
        ' Wait for it to be done.    
    End Sub

    Public Sub DoBusyWork(target As Object)
        Try
            Dim processor As Integer = CInt(target)
            Dim tr As Thread = Thread.CurrentThread
            If Environment.ProcessorCount > 1 Then
                SetThreadAffinityMask(GetCurrentThread(), New IntPtr(1 << processor))
            End If
            CalculatePI.Process(PiSignificantDigits)
        Catch ex As Exception
            _ex = ex
        End Try
    End Sub

    Public _ex As Exception = Nothing

End Class

' Source:
' http://omegacoder.com/?p=91

Public Class CalculatePI

    '     Computation of the n'th decimal digit of \pi with very little memory.      
    '     Written by Fabrice Bellard on January 8, 1997.      
    '     We use a slightly modified version of the method described by Simon      
    '     Plouffe in "On the Computation of the n'th decimal digit of various
    '     transcendental numbers" (November 1996). We have modified the algorithm
    '     to get a running time of O(n^2) instead of O(n^3log(n)^3).     
    '     
    '     This program uses mostly integer arithmetic. It may be slow on some 
    '     hardware where integer multiplications and divisions must be done
    '     by software. We have supposed that 'int' has a size of 32 bits. If 
    '     your compiler supports 'long long' integers of 64 bits, you may use
    '     the integer version of 'mul_mod' (see HAS_LONG_LONG).      
    '     
    '     Call this static to use. 

    Public Shared Function Process(digits As Integer) As String
        Dim result As New StringBuilder()
        result.Append("3.")
        Dim StartTime As DateTime = DateTime.Now
        If digits > 0 Then
            For i As Integer = 0 To digits - 1 Step 9
                Dim ds As String = CalculatePiDigits(i + 1)
                Dim digitCount As Integer = Math.Min(digits - i, 9)
                If ds.Length < 9 Then
                    ds = String.Format("{0:D9}", Integer.Parse(ds))
                End If
                result.Append(ds.Substring(0, digitCount))
            Next
        End If
        Return result.ToString()
    End Function

    Private Shared Function mul_mod(a As Integer, b As Integer, m As Integer) As Integer
        Return CInt((CLng(a) * CLng(b)) Mod m)
    End Function
    ' return the inverse of x mod y 

    Private Shared Function inv_mod(x As Integer, y As Integer) As Integer
        Dim q As Integer, u As Integer, v As Integer, a As Integer, c As Integer, t As Integer
        u = x
        v = y
        c = 1
        a = 0
        Do
            q = v \ u
            t = c
            c = a - q * c
            a = t
            t = u
            u = v - q * u
            v = t
        Loop While u <> 0

        a = a Mod y
        If a < 0 Then
            a = y + a
        End If
        Return a
    End Function
    ' return (a^b) mod m 


    Private Shared Function pow_mod(a As Integer, b As Integer, m As Integer) As Integer
        Dim r As Integer, aa As Integer
        r = 1
        aa = a
        While True
            If (b And 1) <> 0 Then
                r = mul_mod(r, aa, m)
            End If

            b = b >> 1

            If b = 0 Then
                Exit While
            End If

            aa = mul_mod(aa, aa, m)
        End While
        Return r
    End Function

    ' return true if n is prime 

    Private Shared Function is_prime(n As Integer) As Boolean
        If (n Mod 2) = 0 Then
            Return False
        End If

        Dim r As Integer = CInt(Math.Truncate(Math.Sqrt(n)))

        For i As Integer = 3 To r Step 2
            If (n Mod i) = 0 Then
                Return False
            End If
        Next
        Return True
    End Function

    ' return the prime number immediatly after n 

    Private Shared Function next_prime(n As Integer) As Integer
        Do
            n += 1
        Loop While Not is_prime(n)
        Return n
    End Function

    Private Shared Function CalculatePiDigits(n__1 As Integer) As String
        Dim av As Integer, vmax As Integer, num As Integer, den As Integer, s As Integer, t As Integer
        Dim N__2 As Integer = CInt(Math.Truncate((n__1 + 20) * Math.Log(10) / Math.Log(2)))
        Dim sum As Double = 0
        Dim a As Integer = 3
        While a <= (2 * N__2)
            vmax = CInt(Math.Truncate(Math.Log(2 * N__2) / Math.Log(a)))
            av = 1
            For i As Integer = 0 To vmax - 1
                av = av * a
            Next
            s = 0
            num = 1
            den = 1
            Dim v As Integer = 0
            Dim kq As Integer = 1
            Dim kq2 As Integer = 1

            For k As Integer = 1 To N__2
                t = k
                If kq >= a Then
                    Do
                        t = t \ a
                        v -= 1
                    Loop While (t Mod a) = 0

                    kq = 0
                End If
                kq += 1
                num = mul_mod(num, t, av)
                t = 2 * k - 1
                If kq2 >= a Then
                    If kq2 = a Then
                        Do
                            t = t \ a
                            v += 1
                        Loop While (t Mod a) = 0
                    End If

                    kq2 -= a
                End If
                den = mul_mod(den, t, av)
                kq2 += 2
                If v > 0 Then
                    t = inv_mod(den, av)
                    t = mul_mod(t, num, av)
                    t = mul_mod(t, k, av)
                    For i As Integer = v To vmax - 1
                        t = mul_mod(t, a, av)
                    Next
                    s += t
                    If s >= av Then
                        s -= av
                    End If
                End If
            Next

            t = pow_mod(10, n__1 - 1, av)
            s = mul_mod(s, t, av)
            sum = (sum + CDbl(s) / CDbl(av)) Mod 1.0
            a = next_prime(a)
        End While
        Dim Result As Integer = CInt(Math.Truncate(sum * 1000000000.0))

        Dim StringResult As String = String.Format("{0:D9}", Result)
        Return StringResult
    End Function

    ' Put a space between every group of 10 digits.     
    Private Shared Function breakDigitsIntoGroupsOf10(digits As String) As String
        Dim result As String = ""
        While digits.Length > 10
            result += digits.Substring(0, 10) & " "
            digits = digits.Substring(10, digits.Length - 10)
        End While
        result += digits
        Return result
    End Function

End Class

Open in new window

0
 
LVL 2

Author Comment

by:RHenningsgard
ID: 36530689
This looks like the soul of a home run, but it will take me a day or two to extract what I need and re-code it into a usable form.  That said, I'm betting you get the "accepted solution" points when I'm done.

Thanks,

Rob---
0
 
LVL 2

Author Closing Comment

by:RHenningsgard
ID: 36537726
Nice dig!  That was exactly the information I needed to address the chore of persuading WinXP to run my thread on a specific core.

Thanks,

Rob---
0

Featured Post

How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

Join & Write a Comment

Well, all of us have seen the multiple EXCEL.EXE's in task manager that won't die even if you call the .close, .dispose methods. Try this method to kill any excels in memory. You can copy the kill function to create a check function and replace the …
Since .Net 2.0, Visual Basic has made it easy to create a splash screen and set it via the "Splash Screen" drop down in the Project Properties.  A splash screen set in this manner is automatically created, displayed and closed by the framework itsel…
Here's a very brief overview of the methods PRTG Network Monitor (https://www.paessler.com/prtg) offers for monitoring bandwidth, to help you decide which methods you´d like to investigate in more detail.  The methods are covered in more detail in o…
This tutorial demonstrates a quick way of adding group price to multiple Magento products.

705 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

15 Experts available now in Live!

Get 1:1 Help Now