// Copyright (C) 2010
// Ajay Vijayvargiya
// This source code was published on CodeProject.com, under CPOL license
// This code CANNOT be used for similar publication on the web or as printed material.
// This can, however, be used as educational reference in educational institutions.
// You are allowed to use the accompanying code in your programs,
// commercial or non commercial.
// This source code is only intended to explicate the Concurrency Runtime in Visual C++ 2010,
// and thus may contain bugs, some logical issues etc.
// The explanation is relevant to Visual C++ 2010 (Compiler version: 16.0)
// http://www.codeproject.com/KB/cpp/parallelcpp.aspx
// For performance benchmarking, simple approach, GetTickCount is used.
// If you prefer, you may use other high performance counters.
// Ignore the importance and non-optimized code, they are just for illustration.
#include "stdafx.h"
#include "CR MFC App.h"
#include "CR MFC AppDlg.h"
#include "afxdialogex.h"
#include <ppl.h>
#include <vector>
#include <algorithm>
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
#define EMPTY_STRING _T("")
bool IsPrimeNumber(int nNumber)
{
bool bIsPrime = true;
for(int i = 3; i <= (int)sqrt((float)nNumber); i+=2)
{
if(nNumber % i == 0)
{
bIsPrime = false;
break;
}
}
return bIsPrime;
}
// CCRMFCAppDlg dialog
CCRMFCAppDlg::CCRMFCAppDlg(CWnd* pParent /*=NULL*/)
: CDialogEx(CCRMFCAppDlg::IDD, pParent)
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
void CCRMFCAppDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
DDX_Control(pDX, IDC_SUM_TO, m_cSumTill);
DDX_Control(pDX, IDC_OUT_LIST, m_cOutList);
DDX_Control(pDX, IDC_PRIME_TO, m_cPrimeTill);
}
BEGIN_MESSAGE_MAP(CCRMFCAppDlg, CDialogEx)
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(IDC_BTN_SUM, &CCRMFCAppDlg::OnBnClickedBtnSum)
ON_EN_SETFOCUS(IDC_SUM_TO, &CCRMFCAppDlg::OnEnSetfocusSumTo)
ON_EN_SETFOCUS(IDC_PRIME_TO, &CCRMFCAppDlg::OnEnSetfocusPrimeTo)
ON_BN_CLICKED(IDC_BTN_PRIME, &CCRMFCAppDlg::OnBnClickedBtnPrime)
ON_BN_CLICKED(IDC_BTN_TASKS, &CCRMFCAppDlg::OnBnClickedBtnTasks)
END_MESSAGE_MAP()
// CCRMFCAppDlg message handlers
BOOL CCRMFCAppDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
// Set the icon for this dialog. The framework does this automatically
// when the application's main window is not a dialog
SetIcon(m_hIcon, TRUE); // Set big icon
SetIcon(m_hIcon, FALSE); // Set small icon
// TODO: Add extra initialization here
return TRUE; // return TRUE unless you set the focus to a control
}
// If you add a minimize button to your dialog, you will need the code below
// to draw the icon. For MFC applications using the document/view model,
// this is automatically done for you by the framework.
void CCRMFCAppDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // device context for painting
SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
// Center icon in client rectangle
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// Draw the icon
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialogEx::OnPaint();
}
}
// The system calls this function to obtain the cursor to display while the user drags
// the minimized window.
HCURSOR CCRMFCAppDlg::OnQueryDragIcon()
{
return static_cast<HCURSOR>(m_hIcon);
}
void CCRMFCAppDlg::OnBnClickedBtnSum()
{
EnableControls(FALSE);
// Designating the "sum" task in different thread
// has no correlation with CR. It is just to make sure
// that UI gets updated on time, and UI doesn't freeze.
AfxBeginThread(PerformParallelSum, this);
}
UINT CCRMFCAppDlg::PerformParallelSum(void* pParam)
{
CCRMFCAppDlg* pDlg = static_cast<CCRMFCAppDlg*>(pParam);
pDlg ->ParallelSumProc();
return 0;
}
/***************************************************************************/
/* Function: ParallelSum */
/***************************************************************************/
/* Demonstrates parallel_for algorithm and the combinable class */
/* It should be noted that each iteration should do something specific, */
/* and not trivial thing like adding the number only. When each iteration. */
/* performs something, it gives performance benefits over serial execution */
/* That's why I sum only number divisible by some number. Had it been just */
/* summation of all numbers, serial version would perform faster, since */
/* combinable::local function would use more of CPU than actual sum. */
/***************************************************************************/
#define DIVISIBLE_BY 7
void CCRMFCAppDlg::ParallelSumProc()
{
using namespace Concurrency;
using namespace std;
LONGLONG nSumTill;
TCHAR strSumTill[64 + 1];
// Can only be number (See resource for IDC_SUM_TO)
m_cSumTill.GetWindowText(strSumTill, 64);
// atoi -> atoi64 -> _wtoi64
// _ttoi64 is just macro for string to __int64 conversion
nSumTill = _ttoi64(strSumTill);
CString strOutput;
unsigned __int64 nSum;
combinable<LONGLONG> Sum;
// This object is just placed to demonstrate more of combinable class.
combinable<vector<LONGLONG>> VectorOfDivisibles;
vector<LONGLONG> Dummy;
DWORD nTickStart, nTickEnd;
// Clear the output first
m_cOutList.ResetContent();
m_cOutList.AddString(L"Accumulating serially (check that only 1 CPU-core is utilized)");;
nTickStart = GetTickCount();
// Execute serially
nSum = 0;
for ( LONGLONG nValue = 1; nValue <= nSumTill; ++nValue)
{
if ( (nValue % DIVISIBLE_BY) == 0)
{
nSum += nValue;
Dummy.push_back(nValue); // Put just to make sure both loops perform same work.
}
}
nTickEnd = GetTickCount();
strOutput.Format(L"The sum is: %I64d", nSum);
m_cOutList.AddString(strOutput);
strOutput.Format(L"Which took %d ms", (nTickEnd - nTickStart));
m_cOutList.AddString(strOutput);
strOutput.Format(_T("Total numbers: %u"), Dummy.size());
m_cOutList.AddString(strOutput);
m_cOutList.AddString(EMPTY_STRING);
m_cOutList.AddString(_T("\nAccumulating parallelly (Multiple cores are being utilized)"));
nTickStart = GetTickCount();
// Execute parallelly
parallel_for( (LONGLONG)1, nSumTill+1, [&](LONGLONG nValue)
{
if ( (nValue % DIVISIBLE_BY) == 0)
{
Sum.local() += nValue;
// Add this number to 'this' thread-specific vector.
VectorOfDivisibles.local().push_back(nValue);
}
});
int nNumberCount = 0 ;
VectorOfDivisibles.combine_each([&nNumberCount](const vector<LONGLONG>& long_vector)
{
nNumberCount += long_vector.size();
});
nTickEnd = GetTickCount();
ULONGLONG nCombineSum = Sum.combine(plus<ULONGLONG>());
strOutput.Format( _T("The sum is: %I64d"), nCombineSum);;
m_cOutList.AddString(strOutput);
strOutput.Format( _T("Which took %d ms"), (nTickEnd - nTickStart));
m_cOutList.AddString(strOutput);
strOutput.Format( _T("Total numbers: %u"), nNumberCount);
m_cOutList.AddString(strOutput);
EnableControls(TRUE);
}
void CCRMFCAppDlg::OnBnClickedBtnPrime()
{
EnableControls(FALSE);
// Designating the "prime find" task in different thread
// has no correlation with CR. It is just to make sure
// that UI gets updated on time, and UI doesn't freeze.
AfxBeginThread(PerformParallelPrimeFind, this);
}
UINT CCRMFCAppDlg::PerformParallelPrimeFind(void* pParam)
{
CCRMFCAppDlg* pDlg = static_cast<CCRMFCAppDlg*>(pParam);
pDlg ->ParallelPrimeFindProc();
return 0;
}
/****************************************************************************/
/* Function: ParallelPrimeFind */
/****************************************************************************/
/* Demonstrates parallel_for_each algorithm and the combinable class */
/* This function first generates few numbers (1 to ELEMENT_COUNT_FOR_PRIME) */
/* Then it shuffles the numbers. */
/* For performance benchmarking of serial and parallel executions, it finds */
/* "count" of prime numbers in this range. Timing of both computation is */
/* rendered to console. */
/****************************************************************************/
void CCRMFCAppDlg::ParallelPrimeFindProc()
{
using namespace Concurrency;
using namespace std;
int nPrimeTill;
TCHAR strSumTill[64 + 1];
// Can only be number (See resource for IDC_SUM_TO)
m_cPrimeTill.GetWindowText(strSumTill, 64);
nPrimeTill = _ttoi(strSumTill);
vector<int> Numbers;
combinable<int> Primes;
int nPrimes;
DWORD nTickStart, nTickEnd;
CString strOutput;
m_cOutList.ResetContent();
// Allocate vector
Numbers.resize(nPrimeTill);
// Generate incrementing numbers
int nCounter = 1;
generate(Numbers.begin(), Numbers.end(), [&nCounter]
{
return nCounter++;
});
// Shuffle the vector
random_shuffle(Numbers.begin(), Numbers.end());
m_cOutList.AddString( _T("Finding primes serially...") );
// Find the prime numbers (count), serially.
// The lambda counts them.
nTickStart = GetTickCount();
nPrimes = 0;
for_each(Numbers.begin(), Numbers.end(), [&nPrimes](int nNumber)
{
if(IsPrimeNumber(nNumber))
nPrimes++;
});
nTickEnd = GetTickCount();
strOutput.Format( _T("\nPrimes found: %d") ,nPrimes);
m_cOutList.AddString( strOutput );
strOutput.Format(_T("Which took %d ms") , (nTickEnd - nTickStart) );
m_cOutList.AddString( strOutput );
m_cOutList.AddString(EMPTY_STRING);
m_cOutList.AddString( _T("Finding primes parallelly..."));
// Find the prime numbers (count), serially.
// The lambda counts them.
nTickStart = GetTickCount();
parallel_for_each(Numbers.begin(), Numbers.end(), [&Primes](int nNumber)
{
if(IsPrimeNumber(nNumber))
{
Primes.local()++;
}
});
nTickEnd = GetTickCount();
nPrimes = Primes.combine(plus<int>());
strOutput.Format( _T("\nPrimes found: %d") ,nPrimes);
m_cOutList.AddString( strOutput );
strOutput.Format(_T("Which took %d ms") , (nTickEnd - nTickStart) );
m_cOutList.AddString( strOutput );
EnableControls(TRUE);
}
void CCRMFCAppDlg::OnBnClickedBtnTasks()
{
EnableControls(FALSE);
// Designating the tasks in different thread
// has no correlation with CR. It is just to make sure
// that UI gets updated on time, and UI doesn't freeze.
AfxBeginThread(PerformParallelInvoke, this);
}
// 5 million / 50 lacs
#define ELEMENT_COUNT_PARALLEL_INVOKE 5000000
// 500 thousand / 5 lacs
#define ELEMENT_COUNT_FOR_PRIME 500000
UINT CCRMFCAppDlg::PerformParallelInvoke(void* pParam)
{
CCRMFCAppDlg* pDlg = static_cast<CCRMFCAppDlg*>(pParam);
pDlg ->ParallelInvokeProc();
return 0;
}
/*****************************************************************************/
/* Function: ParallelInvoke **/
/*****************************************************************************/
/* It runs three tasks/function in serial and then parallel, and displays the*/
/* time taken. */
/* The first two routines, finds the count of even/odd number, and puts those*/
/* number in string format in a vector. Converting and inserting into vector */
/* is just to make sure that each function "does something" CPU intensive. */
/* Since, I did not use sleep, UI, file-IO or other external factors, doing */
/* was only choice. */
/* This third routine does the same with prime numbers. */
/*****************************************************************************/
void CCRMFCAppDlg::ParallelInvokeProc()
{
using namespace Concurrency;
using namespace std;
CString strOutput;
ULONGLONG nEvenSum, nOddSum, nPrimeSum;
vector<string> Evens, Primes, Odds;
DWORD nTickStart, nTickEnd;
// Let's define few lambdas locally and store them into 'auto' variables
//.....//
auto EvenAccumulator = [&nEvenSum, &Evens]
{
nEvenSum = 0;
char sBuffer[64]; // Just a placeholder
// Non optimized way to sum...
for (int nValue = 1; nValue <= ELEMENT_COUNT_PARALLEL_INVOKE; ++nValue)
{
if ( nValue%2 == 0 )
{
nEvenSum += nValue;
sprintf_s(sBuffer, "%d", nValue);
Evens.push_back(sBuffer);
}
}
};
auto OddAccumulator = [&nOddSum, &Odds]
{
nOddSum = 0;
char sBuffer[64]; // Just a placeholder
// Non optimized way to sum...
for (int nValue = 1; nValue <= ELEMENT_COUNT_PARALLEL_INVOKE; ++nValue)
{
if ( nValue%2 != 0 )
{
nOddSum += nValue;
sprintf_s(sBuffer, "%d", nValue);
Odds.push_back(sBuffer);
}
}
};
auto PrimesCounter= [&Primes, &nPrimeSum]
{
char sBuffer[64]; // Just a placeholder
// Non optimized way to sum...
for (int nValue = 1; nValue <= ELEMENT_COUNT_FOR_PRIME; ++nValue)
{
if( IsPrimeNumber(nValue) )
{
nPrimeSum += nValue;
sprintf_s(sBuffer, "%d", nValue);
Primes.push_back(sBuffer);
}
}
};
//.....//
m_cOutList.ResetContent();
// Do it serially...
m_cOutList.AddString(_T("\nExecuting serially..."));
m_cOutList.AddString(EMPTY_STRING);
nTickStart = GetTickCount();
EvenAccumulator();
OddAccumulator();
PrimesCounter();
nTickEnd = GetTickCount();
strOutput.Format( _T("Serial execution took %d ms") ,(nTickEnd - nTickStart));
m_cOutList.AddString(strOutput);
strOutput.Format( _T("Even sum is: %I64d"), nEvenSum);
m_cOutList.AddString(strOutput);
strOutput.Format( _T("Odd sum is: %I64d"), nOddSum);
m_cOutList.AddString(strOutput);
strOutput.Format( _T("Primes count is: %d"), Primes.size());
m_cOutList.AddString(strOutput);
m_cOutList.AddString(EMPTY_STRING);
Primes.clear();
Odds.clear();
Evens.clear();
// Do it parallelly
m_cOutList.AddString(_T("\nExecuting parallelly..."));;
m_cOutList.AddString(EMPTY_STRING);
nTickStart = GetTickCount();
parallel_invoke(
EvenAccumulator,
OddAccumulator,
PrimesCounter);
nTickEnd = GetTickCount();
strOutput.Format( _T("Parallel execution took %d ms") ,(nTickEnd - nTickStart));
m_cOutList.AddString(strOutput);
strOutput.Format( _T("Even sum is: %I64d"), nEvenSum);
m_cOutList.AddString(strOutput);
strOutput.Format( _T("Odd sum is: %I64d"), nOddSum);
m_cOutList.AddString(strOutput);
strOutput.Format( _T("Primes count is: %d"), Primes.size());
m_cOutList.AddString(strOutput);
EnableControls(TRUE);
}
void CCRMFCAppDlg::EnableControls( BOOL bEnable )
{
GetDlgItem(IDC_BTN_SUM)->EnableWindow(bEnable);
//GetDlgItem(IDC_SUM_TO)->EnableWindow(bEnable);
GetDlgItem(IDC_BTN_PRIME)->EnableWindow(bEnable);
//GetDlgItem(IDC_PRIME_TO)->EnableWindow(bEnable);
GetDlgItem(IDC_BTN_TASKS)->EnableWindow(bEnable);
}
void CCRMFCAppDlg::OnEnSetfocusSumTo()
{
// Set my buddy button to be default, when I have focus!
SetDefID(IDC_BTN_SUM);
}
void CCRMFCAppDlg::OnEnSetfocusPrimeTo()
{
// Set my buddy button to be default, when I have focus!
SetDefID(IDC_BTN_PRIME);
}