<h3>Introduction</h3>
With Intel CPUs making up nearly <a href="https://www.cpubenchmark.net/market_share.html">80% of the CPU market</a> and <a href="http://jonpeddie.com/press-releases/details/gpu-market-upintel-and-nvidia-graphics-winners-in-q4-amd-down/">66% of computers using integrated graphics</a> one can easily argue that integrated graphics devices represent one of the greatest markets for GPU-accelerated computing. Here at ArrayFire, we have long recognized the potential of these devices and offer built-in support for Intel CPUs, GPUs, and AMD APUs in the OpenCL backend of our <a href="http://www.arrayfire.com">ArrayFire</a> GPU computing library. Yet one common theme for debate in the office has been how the hardware performs on different operating systems with different drivers across hardware revisions.

To answer these questions (and, perhaps, to win some intra-office geek cred) I decided to write a series of blog posts about Intel's GPU OpenCL performance. In this first installment I will compare the performance of OpenCL on a 3rd generation Intel processor on Linux and Windows.
<h3>Monte Carlo Pi estimation</h3>
To evaluate the performance of Intel's hardware I took a textbook problem of computing Pi using a Monte Carlo method. This is a classic example of a memory bound application where the time to read/write data from/to memory dominates most of the computation time. Thus, throwing N-more compute cores at the problem may not always achieve a linear acceleration unless careful attention to memory access patterns and cache usage is performed.

The Monte Carlo process for computing pi is fairly simple, consisting of three primary steps:
<ol>
	<li>Generate a series of random (x,y) pairs from within the <a href="http://en.wikipedia.org/wiki/Unit_square">unit square</a></li>
	<li>Determine which of these samples reside within the <a href="http://en.wikipedia.org/wiki/Unit_circle">unit circle</a></li>
	<li>Compute the ratio of the samples that reside within the square to those which reside within the circle and then multiply by four (to account for this being only one quadrant of a circle).</li>
</ol>
There is a great visualization of this process and a demonstration of how the <a href="http://commons.wikimedia.org/wiki/File:Pi_30K.gif">number of samples impacts the Pi estimate on wikipedia</a>.

If you were to implement this in C/C++, your code might look like this:
<pre class="lang:default decode:true" title="Sample CPU code for Monte Carlo Pi estimation">...

int count = 0;
float x, y;
vector&lt;float&gt; randomNums(2*samples);

// init random numbers within the unit square.
// Pack as (x,y) pairs
for(int i = 0; i &lt; 2*samples; i++)	{
	randomNums[i] = float(rand()) / RAND_MAX;
}

// count how many samples reside in the circle
for(int i = 0; i &lt; samples; i++)
{
	x = randomNums[2*i];
	y = randomNums[2*i + 1];

	if(x*x + y*y &lt; 1.0)
		count++;
}

// calculate the estimate and print the result
float pi_estimate = 4 * float(count) / samples;

...</pre>
<h3>Benchmark methods</h3>
We implemented four different versions of the Pi problem with small changes in the implementation which test various aspects of the hardware and software. These five implementations are:
<ol>
	<li>Single core CPU - A direct implementation of the code listing above</li>
	<li>A naive port of the CPU code to the GPU with no optimizations. Region testing is performed on the GPU and the result is summed on the CPU</li>
	<li>An effort to reduce the workload of the CPU by partially summing the region testing results on the GPU</li>
	<li>A classic GPU optimization using coalesced memory load/stores</li>
	<li>And a final integrated-device specific optimization that uses <a href="http://sandbox.arrayfire.com/zero-copy-on-integrated-gpus/">zero-copy memory buffers</a></li>
</ol>
The source code for these tests are published in my "<a href="https://github.com/bkloppenborg/blogcode">blog code</a>" GitHub repository.

For these tests we used a few-year old ASUS N56V laptop we had in the office which features an Intel i7-3610QM CPU with a HD Graphics 4000 GT2 GPU. This particular GPU has 16 execution units which can host up to 128 concurrent threads. From the <a href="http://download.intel.com/support/processors/corei7/sb/core_i7-3600_m.pdf">specification sheet</a>, the CPU maximum performance is 106 GFLOPs whereas the GPU can do slightly better at 140 GFLOPs. We installed Window 7 (Intel OpenCL GPU driver 10.18.10.395; Intel OpenCL CPU drivers 5.1.0.25) and Ubuntu 14.10 (GPU driver Beignet 1.1 from git acfc2a2 and CPU driver "Build 92"). In future blog posts we will cover 4th and 5th generation hardware (including Iris pro).
<h3>Results and discussion</h3>
On Linux a single core CPU took <span data-sheets-value="[null,3,null,31583]" data-sheets-userformat="[null,null,513,[null,0],null,null,null,null,null,null,null,null,0]">31.583 ms to execute on a sample size of 20 million sample points. Interestingly, the same code on Windows too over 66 ms to execute on the same sample size (in release mode, with /02 and high power settings selected). This curious result isn't particularly important as the OpenCL performance on the CPU does not appear to be degraded between operating systems.</span>

Below we plot the speed up of our OpenCL kernels relative to the performance of the Linux single core CPU result above for both the i7-3610QM CPU and HD Graphics 4000 GPU.

<iframe width="694" height="371" seamless frameborder="0" scrolling="no" src="https://docs.google.com/spreadsheets/d/1vMeMPs4d78wsbURINCKmAFDFBwIFzpAl438Q-geoeNs/pubchart?oid=2103791532&amp;format=interactive"></iframe>

We see that the naive port of the CPU code directly to OpenCL is slower than the pure CPU implementation by a factor of four. This result is not terribly surprising once you consider the poorly conceived implementation encounters a full O(N) cost for data transfer and summing region test result for the 20 million sample points.

The OCL reduction kernel offloads some of the CPU's work by partially summing the region detection results on the GPU. This tweak increased the performance of the kernel between 4x (for the OpenCL on the GPU) to 10x (for OpenCL on the CPU). Curiously, the CPU kernels on Ubuntu and Windows perform very well, achieving speed-ups of about 3x the CPU. This is still below the theoretical 4x speed improvement one would expect on this quad-core CPU namely due to thread management and poor memory access patterns.

Lastly we will discuss the coalesced memory and zero copy kernels. The optimization here involves changing the memory access pattern from using strieded memory access:
<pre class="lang:default decode:true" title="Sample CPU code for Monte Carlo Pi estimation">x = randomNums[2*i];
y = randomNums[2*i + 1];</pre>
to using OpenCL's float2 data type to access sequential elements, thus improving utilization of the memory bus. This technique improved throughput by 2.2x on Ubuntu and 3.25x on Windows. This makes the GPU about twice as fast as our initial CPU implementation. We see little to no acceleration in using zero copy buffers for this problem because the problem size for the final CPU sum is quite small (on the order of 1000 elements).
<h3>Conclusion and a preview of the next post</h3>
For the Monte Carlo Pi problem, it appears Ubuntu and Beignet achieve slightly better performance than the current Windows Intel HD Graphics OpenCL drivers; however, given the limited number of trials performed (and lack of statistical error bars) we may conservatively state that the two OpenCL implementations perform similarly on the same hardware.

We found that using OpenCL on the GPU achieves better acceleration than OpenCL on the CPU in general. In one of our kernels, OpenCL on the i7-3610QM CPU was slightly faster than on the GPU, but this is probably due to automatic vectorization in the CPU OpenCL backend for this particular kernel.

In our next post we will compare the performance of OpenCL on 3rd, 4th, and 5th generation Intel processors using both memory and compute-bound kernels.