This walkthrough presents Unified Shared Memory (USM) as an alternative to buffers for managing and accessing memory from both the host and device. The program determines whether each point in a two-dimensional complex plane belongs to the set, employing parallel computing patterns and SYCL. We use a Mandelbrot sample in this code walkthrough to delve into USM.
Download the Mandelbrot source from GitHub.
Summary of Unified Shared Memory
- SYCL includes USM as a language feature.
- USM necessitates hardware support for a unified virtual address space, which ensures consistent pointer values between the host and device.
- The host allocates all memory, but it provides three distinct allocation types:
- Host: This type is located on the host and accessible by either the host or device.
- Device: This type is located on the device and only accessible by the device.
- Shared: The location of this type can be either the host or device (managed by the compiler), and it's accessible by either the host or device.
Include Headers
In addition to some other common libraries, the Mandelbrot code sample utilizes Sean's Toolbox* (STB) for data visualization. The STB libraries facilitate the reading and writing of image files.
The Mandelbrot code sample also uses functionality provided by dpc_common:
main.cpp Driver Functions
The driver function, main.cpp, contains the infrastructure to execute and evaluate the computation of the Mandelbrot set.
Queue Creation
In main, the queue is created using the default selector. This first tries to launch a kernel code on the GPU, and if no compatible device is found, it falls back to the Host/CPU.
Show Device
The ShowDevice() function displays important information about the selected device.
Execute
The Execute() function initializes the MandelParallelUsm object, evaluates the Mandelbrot set using it and outputs the results.
Mandelbrot USM Usage
Mandel Parameter Class
The MandelParameter struct contains all the necessary functionality for calculating the Mandelbrot set.
ComplexF Datatype
The MandelParameter defines a datatype, ComplexF, representing a complex floating-point number.
Point Function
The Point() function takes a complex point, c, as an argument and determines if it belongs to the Mandelbrot set. The function checks for how many iterations (up to an arbitrary max_iterations_) that the parameter, z, stays bound to given the recursive function, zn+1 = (zn)2 + c, where z0 = 0. Then, it returns the number of iterations.
Scale Row and Column
The scale functions convert row/column indices into coordinates within the complex plane. This conversion is necessary to transform array indices into their corresponding complex coordinates. You can see this application in the following Mandle Parallel USM Class section.
Mandle Class
The Mandel class serves as the parent class from which MandelParallelUsm inherits. It contains member functions for outputting the data visualization, which we address in the Other Functions section.
Member Variables
- MandelParameters p_: A MandelParameters object.
- int *data_: A pointer to the memory for storing the output data.
Mandle Parallel USM Class
This class, derived from the Mandel class, manages all the device code for offloading the Mandelbrot calculation using USM.
Constructor Device Initialization
The MandelParallelUsm constructor first invokes the Mandel constructor, which assigns the argument values to their corresponding member variables. It passes the address of the queue object to the member variable, q, for later use in launching the device code. Finally, it calls the Alloc() virtual member function.
Alloc USM Initialization
The Alloc() virtual member function, overridden in the MandelParallelUsm class, enables USM. It calls malloc_shared(), which creates and returns the address to a block of memory shared across the host and device.
Launch the Kernel with Evaluate
The Evaluate() member function launches the kernel code and computes the Mandelbrot set.
Inside parallel_for(), the work item ID (index) is mapped to row and column coordinates. These coordinates are used to construct a point in the complex plane using the ScaleRow()/ScaleCol() functions. The MandelParameters Point() function is then called to determine if the complex point belongs to the Mandelbrot set, with its result written to the corresponding location in shared memory.
Free Shared Memory with Destructor
The destructor ensures no memory leaks by freeing the shared memory through a call to the Free() member function.
Other Functions
Producing a Basic Visualization of the Mandelbrot Set
The Mandel class also includes member functions for data visualization. The WriteImage() function generates a PNG image representation of the data, where each pixel represents a point on the complex plane, and its luminosity signifies the iteration depth calculated by the Point() function.
Example Image of Data Output
The Mandel class’s Print()member function generates a visualization similar to what is written to stdout.
Summary
This walkthrough demonstrates how to use familiar C/C++ patterns to manage data within host and device memory, using Mandelbrot as a test case.