Introduction to the ArrayFire array

Brian Kloppenborg ArrayFire Leave a Comment

The fundamental primitive of the ArrayFire Library is our container object, the array. As the next post in our blog series, “Learning ArrayFire from scratch“, we will describe how to construct arrays, what datatypes arrays support, indexing, how to query an array for various properties (content, dimensions), and how to write mathematical expressions that involve arrays.

ArrayFire abstracts away much of the details of programming parallel architectures by providing a high-level container object, the array, that represents data stored on a CPU, GPU, FPGA, or other type of accelerator. This abstraction permits developers to write massively parallel applications in a high-level language where they need not be concerned about low-level optimizations that are frequently required to achieve high throughput on most parallel architectures.

Creating ArrayFire Arrays

Arrays represent memory on a supported device. You can create arrays of uninitialized memory using the array constructor:

// Arrays may be created using the af::array constructor and dimensioned
// as 1D, 2D, 3D; however, the values in these arrays will be undefined
array undefined_1D = array(100);        // 1D array with 100 elements
array undefined_2D = array(10, 100);    // 2D array of size 10 x 100
array undefined_3D = array(10, 10, 10); // 3D array of size 10 x 10 x 10

Or using one of many constructors that initialize memory:

// Create an array of zeros of length three:
array zeros      = constant(0, 3);

// Generate a 1x4 array of uniformly distributed [0,1] random numbe// using af::randu():
array rand1      = randu(1, 4);
    
// Make a 2x2 array (or matrix, if you prefer) of random numbers
// sampled from a normal distribution using af::randn():
array rand2      = randn(2, 2);

// Construct a 3x3 identity matrix. 
array iden       = af::identity(3, 3);

Lastly, arrays may be populated with data from the host using our copy constructor:

// Create a six-element array on the host
float hA[] = {0, 1, 2, 3, 4, 5};
    
// Using the array copy constructor, we can create a 2x3
// matrix from this data:
array A(2, 3, hA);

Arrays can also be created from CUDA device pointers or OpenCL memory buffers. Please consult the documentation on these aspects.

Supported data types

ArrayFire arrays support standard integral and complex data types found in C/C++. The data type for an array is specified by providing an (optional) final parameter to any of the array constructors. The type field and supported types are as follows:

  • b8 8-bit boolean values (bool)
  • f32 real single-precision (float)
  • c32 complex single-precision (cfloat)
  • s32 32-bit signed integer (int)
  • u32 32-bit unsigned integer (unsigned)
  • f64 real double-precision (double)
  • c64 complex double-precision (cdouble)
  • s64 64-bit signed integer (intl)
  • u64 64-bit unsigned integer (uintl)
  • s16 16-bit signed integer (short)
  • u16 16-bit unsigned integer (unsigned short)

For example, if you wanted to construct a 2×1 (column vector) of uniformly distributed 32-bit complex numbers (c32 data type), you would use the following code:

// Create a 2x1 column vector of uniformly distributed
// 32-bit complex values:
array randcplx   = randu(2, 1, c32);

Indexing

Like all functions in ArrayFire, indexing is also executed in parallel on the OpenCL/CUDA device. Because of this, indexing becomes part of a JIT operation and is accomplished using parentheses instead of square brackets (i.e. as A(0) instead of A[0]). To index af::arrays you may use one or a combination of the following functions:

  • integer scalars
  • seq() representing a linear sequence
  • end representing the last element of a dimension
  • span representing the entire dimension
  • row(i) or col(i) specifying a single row/column
  • rows(first,last) or cols(first,last) specifying a span of rows or columns

Here are a few examples of these indexing functions in operation:

// Make an 3x3 array containing numbers 1-9:
array A = array(seq(1,9), 3, 3);

A(0);            // first element
A(0,1);          // first row, second column
A(end);          // last element
A(-1);           // also last element
A(end-1);        // second-to-last element
A(1,span);       // second row
A.row(end);      // last row
A.cols(1,end);   // all but first column

For further examples, see the documentation on indexing.

Querying existing arrays for their properties

ArrayFire array objects carry some ancillary information which can be quite useful when developing applications. For example, once you have an array A , you can query for:

  • Number and size of dimensions via. A.numdims()  and A.dims(int)  functions.
  • The size of the array in bytes with A.bytes()
  • The underlying data type using A.type() or one of many type-specific queries.
  • Whether or not the array is complex or real using A.iscomplex()  or A.isreal()
  • If the data is scalar, A.isscalar() , a vector, A.isvector() , a row, A.isrow() , or a column, A.iscolumn() .

Writing mathematical expressions

ArrayFire features an intelligent Just-In-Time (JIT) compilation engine that converts expressions using arrays into the smallest number of CUDA/OpenCL kernels. For most operations on arrays, ArrayFire functions like a vector library. That means that an element-wise operation, like c[i] = a[i] + b[i]  in C, would be written more concisely without indexing, like c = a + b . When there are multiple expressions involving arrays, ArrayFire’s JIT engine will merge them together. This “kernel fusion” technology not only decreases the number of kernel calls, but, more importantly, avoids extraneous global memory operations. Our JIT functionality extends across C/C++ function boundaries and only ends when a non-JIT function is encountered or a synchronization operation is explicitly called by the code. Thus writing mathematical functions in ArrayFire is as simple as writing the equations themselves!

ArrayFire provides hundreds of functions for element-wise operations. All of the standard operators (e.g. +,-,*,/) are supported as are most transcendental functions (sin, cos, log, sqrt, etc.).

In addition to mathematical functions, ArrayFire also includes frequently used computer vision functions (FAST, ORB, SIFT), and image processing functions (colorspace conversion, filters, histograms, transformations, and morphological operations).

 

Leave a Reply

Your email address will not be published.