Playing with ArrayFire: 04 First Know Array (1)


Preface

In "Playing with ArrayFire: 03 The First ArrayFire Program", we have created the first ArrayFire program, but did not have a deep understanding of the usage of ArrayFire. In this article, we will continue to learn the specific usage of ArrayFire.


1. Data types supported by array

     ArrayFire provides a general container object to perform functions and mathematical operations on the array . The array can represent many different basic data types, as shown in the following table. And, unless otherwise specified, the default data type of the array is f32 (real single-precision floating-point number).

f32 Real single precision floating point float
c32 Complex single precision floating point cfloat
f64 Real double precision floating point double
c64 Complex double-precision floating-point number cdouble
b8 8-bit boolean bool
s32 32-bit signed integer int
u32 32-bit unsigned integer unsigned
u8 8-bit unsigned integer unsigned char
s64 4-bit signed integer intl
s16 6-bit signed integer short
u16 16-bit unsigned integer unsigned short

Two, create and fill the array

     array represents stored on the device memory . Therefore, creating and filling arrays will consume memory on the device, which can only be released when the array object goes out of scope . Since device memory allocation can be expensive, ArrayFire also includes a memory manager , which will reuse device memory whenever possible.
    When you need to use the array type, you can create an array . Below we show how to create 1D, 2D and 3D arrays with uninitialized values:

    // 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(100);        // 1D array with 100 elements
    array undefined_2D(10, 100);    // 2D array of size 10 x 100
    array undefined_3D(10, 10, 10); // 3D array of size 10 x 10 x 10

    However, uninitialized memory may not be useful in the application. ArrayFire provides several convenient functions to create arrays containing pre-filled values, including constants, uniform random numbers, uniform normal distribution numbers, and identity matrix:

    // Generate an array of size three filled with zeros.
    // If no data type is specified, ArrayFire defaults to f32.
    // The af::constant function generates the data on the device.
    array zeros      = constant(0, 3);
    // Generate a 1x4 array of uniformly distributed [0,1] random numbers
    // The af::randu function generates the data on the device.
    array rand1      = randu(1, 4);
    // Generate a 2x2 array (or matrix, if you prefer) of random numbers
    // sampled from a normal distribution.
    // The af::randn function generates data on the device.
    array rand2      = randn(2, 2);
    // Generate a 3x3 identity matrix. The data is generated on the device.
    array iden       = af::identity(3, 3);
    // Lastly, create a 2x1 array (column vector) of uniformly distributed
    // 32-bit complex numbers (c32 data type):
    array randcplx   = randu(2, 1, c32);

     The array can also be filled with data on the host. E.g:

    // Create a six-element array on the host
    float hA[] = {
    
    0, 1, 2, 3, 4, 5};
    // Which can be copied into an ArrayFire Array using the pointer copy
    // constructor. Here we copy the data into a 2x3 matrix:
    array A(2, 3, hA);
    // ArrayFire provides a convenince function for printing af::array
    // objects in case you wish to see how the data is stored:
    af_print(A);
    // This technique can also be used to populate an array with complex
    // data (stored in {
    
    {real, imaginary}, {real, imaginary},  ... } format
    // as found in C's complex.h and C++'s <complex>.
    // Below we create a 3x1 column vector of complex data values:
    array dB(3, 1, (cfloat*) hA); // 3x1 column vector of complex numbers
    af_print(dB);

     ArrayFire also supports array initialization from GPU memory . For example, using CUDA, you can directly call cudaMemcpy to fill the array :

    // Create an array on the host, copy it into an ArrayFire 2x3 ArrayFire array
    float host_ptr[] = {
    
    0,1,2,3,4,5};
    array a(2, 3, host_ptr);
    // Create a CUDA device pointer, populate it with data from the host
    float *device_ptr;
    cudaMalloc((void**)&device_ptr, 6*sizeof(float));
    cudaMemcpy(device_ptr, host_ptr, 6*sizeof(float), cudaMemcpyHostToDevice);
    // Convert the CUDA-allocated device memory into an ArrayFire array:
    array b(2,3, device_ptr, afDevice); // Note: afDevice (default: afHost)
    // Note that ArrayFire takes ownership over `device_ptr`, so memory will
    // be freed when `b` id destructed. Do not call cudaFree(device_ptr)!

     OpenCL has similar functions. If you want to mix ArrayFire with CUDA or OpenCL code, we recommend that you check the CUDA Interoperability or OpenCL Interoperability page for detailed instructions.

Three, the content, size and attributes of the array

     ArrayFire provides some functions to determine the characteristics of an array , including functions for printing content, querying dimensions and other characteristics.
     The af_print function can be used to print the generated array or any expression involving array :

    // Generate two arrays
    array a = randu(2, 2);
    array b = constant(1, 2, 1);
    // Print them to the console using af_print
    af_print(a);
    af_print(b);
    // Print the results of an expression involving arrays:
    af_print(a.col(0) + b + .4);

     The dimension of the array can be determined by the dim4 object, or it can be determined by directly accessing the dimension using the dims() and numdims() functions:

    // Create a 4x5x2 array of uniformly distributed random numbers
    array a = randu(4,5,2);
    // Determine the number of dimensions using the numdims() function:
    printf("numdims(a)  %d\n",  a.numdims()); // 3
    // We can also find the size of the individual dimentions using either
    // the `dims` function:
    printf("dims = [%lld %lld]\n", a.dims(0), a.dims(1)); // 4,5
    // Or the elements of a af::dim4 object:
    dim4 dims = a.dims();
    printf("dims = [%lld %lld]\n", dims[0], dims[1]); // 4,5

    In addition to dimensions, arrays also have other properties, such as determining the underlying type and size (in bytes). And you can also determine whether the array is empty, real/complex, row/column, scalar, or vector:

    // Get the type stored in the array. This will be one of the many
    // `af_dtype`s presented above:
    printf("underlying type: %d\n", a.type());
    // Arrays also have several conveience functions to determine if
    // an Array contains complex or real values:
    printf("is complex? %d    is real? %d\n", a.iscomplex(), a.isreal());
    // if it is a column or row vector
    printf("is vector? %d  column? %d  row? %d\n", a.isvector(), a.iscolumn(), a.isrow());
    // and whether or not the array is empty and how much memory it takes on
    // the device:
    printf("empty? %d  total elements: %lld  bytes: %lu\n", a.isempty(), a.elements(), a.bytes());

Fourth, the mathematical expression of array

     ArrayFire provides a smart just-in-time (JIT) compilation engine that can convert expressions that use array into a minimal CUDA/OpenCL kernel. For most operations of array , ArrayFire function is similar to vector library. This means that element operations like c[i] = a[i] + b[i] in C can 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 reduces the number of kernel calls, but more importantly, avoids external global memory operations. Our JIT function extends the C/C++ function boundary and only ends when it encounters a non-JIT function or the code explicitly calls a synchronous operation.
     ArrayFire provides hundreds of functions for element manipulation. Supports all standard operators (such as +, -, *, /), as well as most transcendental functions (sin, cos, log, sqrt, etc.). Here are some examples:

    array R = randu(3, 3);
    af_print(constant(1, 3, 3) + af::complex(sin(R)));  // will be c32
    // rescale complex values to unit circle
    array a = randn(5, c32);
    af_print(a / abs(a));
    // calculate L2 norm of vectors
    array X = randn(3, 4);
    af_print(sqrt(sum(pow(X, 2))));     // norm of every column vector
    af_print(sqrt(sum(pow(X, 2), 0)));  // same as above
    af_print(sqrt(sum(pow(X, 2), 1)));  // norm of every row vector

Five, mathematical constants

     ArrayFire contains some platform-independent constants, such as Pi, NaN and Inf. If ArrayFire does not have the constants you need, you can use the af::constant array constructor to create your own constants.
    Constants can be used in all functions of ArrayFire. Below we will demonstrate their use in element selection and mathematical expressions:

    array A = randu(5,5);
    A(where(A > .5)) = af::NaN;
    array x = randu(10e6), y = randu(10e6);
    double pi_est = 4 * sum<float>(hypot(x,y) < 1) / 10e6;
    printf("estimation error: %g\n", fabs(Pi - pi_est));

    Please note that our constants may sometimes conflict with macro definitions in standard header files. When this happens, please use the af:: namespace to refer to our constants.


Guess you like

Origin blog.csdn.net/weixin_42467801/article/details/113574240