Share via


Calculate Pi to measure processor performance

We know that computers can calculate very quickly, but how do we compare performance between code? I know that processors have been improving immensely since my first processor in 1971 (see https://blogs.msdn.microsoft.com/calvin_hsia/2005/10/30/my-toys-over-the-years/ ). As improvements come to processors, not all programs take advantage of them. As processor manufacturers come up with new improvements (such as 64 bit, SIMD SSE and AVX), programs must be recompiled to take advantage of the new available instructions. However, once so changed, the programs won’t work on existing computers that do not have these features.

I needed a math intensive calculation, that takes a while, so I used the Leibniz series to calculate Pi. The goal of the code below is to measure the time to calculate Pi using various options. The algorithm says
Pi= 4/1 – 4/3 + 4/5 – 4/7 + 4/9….

Which settings yield the fastest program for you? 32 bit? 64 bit? AVX? 

(as an aside, does your program run slower when running under the debugger?)

Start Visual Studio (most versions will do).
Use Visual Studio Online to create a free online account which can host your programming projects. It can store your source code using either GIT or Team Foundation, allowing you to store multiple versions of your code, allowing various computers you may use to sync and coordinate changes. You can also invite others to be a part of your team project.
I like to start this way because it creates a local repo that allows local commits that’s easy to push to VSOnline but it doesn’t require a push
 
From Team Explorer control bar, choose Projects->New Repository. Name it “CalcPi”
 
Then click on New… Solution->C++->Win32 Project->”CalcPi”. Click on Finish to accept all the defaults for the Win32 Project Wizard.
Paste the code below to replace the contents of “CalcPi.cpp”

Let’s add a menu item “Run” with the short cut key “ctrl+R”. Double click the CaclPi.Rc file in the solution explorer to open the resource editor. Drill into CalcPi.rc->Menu->IDC_CALCPI
Right click the Exit item->Insert New->”Run”   
 image

This generates a “ID_FILE_RUN” definition in the resource.h file
Drill into CalcPi.rc->Accelerator->IDC_CALCPI
Create a line for “ID_FILE_RUN” that specifies a Ctrl-R accelerator key:
 image

Experiment with the processor math functions. For example: Project->Properties->C/C++->Code Generation->Enable Enhanced Instruction Set->”Advances Vector Extensions 2 (/arch:AVX2)”
Make a 64 bit version of this C++ program: Build->Options->Configuration Manager-> Active Solution Platform-><New>

You can observe the assembly output by hitting breakpoints and viewing disassembly (Ctrl-F11). Or you can create an Assembly listing: Project->Properties->C/C++->Output Files->Assembler Output->”Assembly, Machine Code and Source (/FAcs)” Open the generated .COD file in the VS editor and examine it. Observe how the release version has optimized the code and inlined a lot of the functions.

 

 

<code>

 // CalcPi.cpp : Defines the entry point for the application.
//

#include "stdafx.h"
#include "CalcPi.h"
#include "string"

using namespace std;

#define MAX_LOADSTRING 100


// Forward declarations of functions included in this code module:
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);

class CCalcPi;
CCalcPi* g_pCCalcPi;

#define M_PI 3.14159265358979323846
// Message handler for about box.
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    UNREFERENCED_PARAMETER(lParam);
    switch (message)
    {
    case WM_INITDIALOG:
        return (INT_PTR)TRUE;

    case WM_COMMAND:
        if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
        {
            EndDialog(hDlg, LOWORD(wParam));
            return (INT_PTR)TRUE;
        }
        break;
    }
    return (INT_PTR)FALSE;
}

class CCalcPi
{
    HWND _hWnd;
    HINSTANCE _hInst;                                // current instance
    WCHAR _szTitle[MAX_LOADSTRING];                  // The title bar text
    WCHAR _szWindowClass[MAX_LOADSTRING];            // the main window class name

    POINTS _frameSize; // size of drawing area
    bool _fIsRunning = false;
    bool _fCancelRequest = false;
    int _nDelayMsecs = 0;
    long long _nIterations = 0;
    DWORD _timeStartmSecs;
    void ShowMsg(int line, LPCWSTR pText, ...)
    {
        va_list args;
        va_start(args, pText);
        wstring strtemp(1000, '\0');
        _vsnwprintf_s(&strtemp[0], 1000, _TRUNCATE, pText, args);
        va_end(args);
        auto len = wcslen(strtemp.c_str());
        strtemp.resize(len);
        HDC hDC = GetDC(_hWnd);
        HFONT hFont = (HFONT)GetStockObject(ANSI_FIXED_FONT);
        HFONT hOldFont = (HFONT)SelectObject(hDC, hFont);
        TextOut(hDC, 0, line * 20, strtemp.c_str(), (int)strtemp.size());
        SelectObject(hDC, hOldFont);
        ReleaseDC(_hWnd, hDC);
    }
    static DWORD WINAPI ThreadRoutine(void *parm)
    {
        CCalcPi *pCCalcPi = (CCalcPi*)parm;
        return pCCalcPi->DoRun();
    }

    void CancelRunning()
    {
        if (_fIsRunning)
        {
            _fCancelRequest = true;
            _fIsRunning = false;
            while (_fCancelRequest)
            {
                Sleep(_nDelayMsecs + 10);
            }
        }
    }
    void EraseBkGnd()
    {
        HDC hDC = GetDC(_hWnd);
        RECT rect;
        GetClientRect(_hWnd, &rect);
        FillRect(hDC, &rect, (HBRUSH)GetStockObject(WHITE_BRUSH));
        ReleaseDC(_hWnd, hDC);
    }

    DWORD DoRun()
    {
        EraseBkGnd();
        ShowMsg(0, L"Calculating Pi  %18.16f", M_PI);
        _nIterations = 0;
        double sum = 0;
        _timeStartmSecs = GetTickCount();
        while (_fIsRunning && !_fCancelRequest)
        {
            // let's calculate pi by infinite series:
            // pi = 4/1 - 4/3 + 4/5 - 4/7 ....
            auto term = 4.0 / (_nIterations * 2 + 1);
            if (_nIterations % 2 == 0)
            {
                sum += term;
            }
            else
            {
                sum -= term;
            }
            if (++_nIterations % 1000000 == 0)
            {
                int nCalcsPerSecond = 0;
                DWORD nTicks = GetTickCount() - _timeStartmSecs;
                if (_fIsRunning)
                {
                    nCalcsPerSecond = (int)(_nIterations / (nTicks / 1000.0));
                }
                auto diff = abs(M_PI - sum);
                auto lg = -log10(diff);
                ShowMsg(2, L"Iter(Mil) = %-10lld  calc(Mil)/sec =%6d %18.15f  %18.15f %8.3f  ", _nIterations / 1000000, nCalcsPerSecond / 1000000, sum, diff, lg);
                if (lg >= 10)
                {
                    ShowMsg(3, L"Reached 10 digits accuracy in %d seconds", nTicks / 1000);
                    break;
                }
            }
        }
        _fCancelRequest = false;
        _fIsRunning = false;
        return 0;
    }

public:
    LRESULT CALLBACK MyWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
        switch (message)
        {
        case WM_ACTIVATEAPP:
            break;
        case WM_SIZE:
        {
            _frameSize = MAKEPOINTS(lParam);
        }
        break;
        case WM_COMMAND:
        {
            int wmId = LOWORD(wParam);
            // Parse the menu selections:
            switch (wmId)
            {
            case ID_FILE_RUN:
                _fIsRunning = !_fIsRunning;
                if (_fIsRunning)
                {
                    auto hThread = CreateThread(
                        nullptr, // sec attr
                        0,  // stack size
                        ThreadRoutine,
                        this, // param
                        0, // creationflags
                        nullptr // threadid
                    );
                }
                else
                {
                    CancelRunning();
                }
                break;
            case IDM_ABOUT:
                DialogBox(_hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
                break;
            case IDM_EXIT:
                DestroyWindow(hWnd);
                break;
            default:
                return DefWindowProc(hWnd, message, wParam, lParam);
            }
        }
        break;
        case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            // TODO: Add any drawing code that uses hdc here...
            EndPaint(hWnd, &ps);
        }
        break;
        case WM_DESTROY:
            CancelRunning();
            PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
        }
        return 0;
    }
    //
    //  FUNCTION: MyRegisterClass()
    //
    //  PURPOSE: Registers the window class.
    //
    ATOM MyRegisterClass(HINSTANCE hInstance)
    {
        WNDCLASSEXW wcex;

        wcex.cbSize = sizeof(WNDCLASSEX);

        wcex.style = CS_HREDRAW | CS_VREDRAW;
        wcex.lpfnWndProc = WndProc;
        wcex.cbClsExtra = 0;
        wcex.cbWndExtra = 0;
        wcex.hInstance = hInstance;
        wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_CALCPI));
        wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
        wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
        wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_CALCPI);
        wcex.lpszClassName = _szWindowClass;
        wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

        return RegisterClassExW(&wcex);
    }

    //
    //   FUNCTION: InitInstance(HINSTANCE, int)
    //
    //   PURPOSE: Saves instance handle and creates main window
    //
    //   COMMENTS:
    //
    //        In this function, we save the instance handle in a global variable and
    //        create and display the main program window.
    //
    BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
    {
        _hInst = hInstance; // Store instance handle in our global variable
                           // Initialize global strings
        LoadStringW(hInstance, IDS_APP_TITLE, _szTitle, MAX_LOADSTRING);
        LoadStringW(hInstance, IDC_CALCPI, _szWindowClass, MAX_LOADSTRING);
        MyRegisterClass(hInstance);

        HWND hWnd = CreateWindowW(_szWindowClass, _szTitle, WS_OVERLAPPEDWINDOW,
            CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);

        if (!hWnd)
        {
            return FALSE;
        }
        _hWnd = hWnd;

        ShowWindow(hWnd, nCmdShow);
        UpdateWindow(hWnd);

        return TRUE;
    }
    int DoMessageLoop()
    {
        HACCEL hAccelTable = LoadAccelerators(_hInst, MAKEINTRESOURCE(IDC_CALCPI));

        MSG msg;

        // Main message loop:
        while (GetMessage(&msg, nullptr, 0, 0))
        {
            if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
            {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
        }

        return (int)msg.wParam;

    }

};

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
    _In_opt_ HINSTANCE hPrevInstance,
    _In_ LPWSTR    lpCmdLine,
    _In_ int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    CCalcPi calcpi;
    g_pCCalcPi = &calcpi;

    // Perform application initialization:
    if (!g_pCCalcPi->InitInstance(hInstance, nCmdShow))
    {
        return FALSE;
    }
    return g_pCCalcPi->DoMessageLoop();
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    return g_pCCalcPi->MyWndProc(hWnd, message, wParam, lParam);
}

</code>