在《玩转 ArrayFire:03 第一个 ArrayFire 程序》中,我们已经创建第一个 ArrayFire 程序,然而却没有深入了解 ArrayFire 的用法,在这一篇中,我们将继续学习 ArrayFire 的具体用法。
ArrayFire 提供了一个通用的容器对象,在 array 上执行函数和数学操作。该数组 array 可以表示许多不同的基本数据类型,如下表。并且,除非另外指定,否则数组的默认数据类型是 f32 (实单精度浮点数)。
f32 | 实单精度浮点数 | float |
---|---|---|
c32 | 复单精度浮点数 | cfloat |
f64 | 实双精度浮点数 | double |
c64 | 复双精度浮点数 | cdouble |
b8 | 8位布尔值 | bool |
s32 | 32位有符号整数 | int |
u32 | 32位无符号整数 | unsigned |
u8 | 8位无符号整数 | unsigned char |
s64 | 4位有符号整数 | intl |
s16 | 6位有符号整数 | short |
u16 | 16位无符号整数 | unsigned short |
array 表示存储在设备上的内存。因此,创建和填充数组将消耗设备上的内存,这些内存只有在数组对象超出作用域时才能释放。由于设备内存分配可能是昂贵的,ArrayFire还包括一个内存管理器,它将在任何可能的情况下重用设备内存。
当需要使用数组类型时,便可以创建 array 。下面我们展示了如何使用未初始化值创建1D、2D和3D数组:
// 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
但是,未初始化的内存在应用程序中可能没有用处。ArrayFire 提供了几个方便的函数来创建包含预填充值的数组,包括常量、均匀随机数、均匀正态分布数和单位矩阵:
// 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);
array 也可以用主机上的数据填充。例如:
// 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 还支持从 GPU 的内存中进行 array 初始化。例如,使用 CUDA 可以直接调用 cudaMemcpy 来填充 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 也有类似的功能。如果您希望将 ArrayFire 与 CUDA 或 OpenCL 代码混合使用,我们建议您查阅 CUDA 互操作性或 OpenCL 互操作性页面以获取详细说明。
ArrayFire 提供了一些函数来确定 array 的特征,这包括用于打印内容、查询维度等各方面特征的函数。
af_print 函数可用于打印已经生成的 array 或任何涉及 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);
array 的维数可以通过 dim4 对象确定,也可以使用 dims( ) 和 numdims( ) 函数直接访问维数来确定:
// 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
除了维度之外, array 还具有其他属性,如确定底层类型和大小(以字节为单位)。并且还可以确定 array 是空、实/复数、行/列、标量还是向量:
// 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());
ArrayFire 提供了一个智能即时(JIT)编译引擎,可以将使用 array 的表达式转换为最少的CUDA/OpenCL内核。对于 array 的大多数操作,ArrayFire 函数类似于 vector 库。这意味着,像 C 中的 c[i] = a[i] + b[i] 这样的元素操作,在没有索引的情况下,可以写得更简洁,像 c = a + b 。当有多个涉及 array 的表达式时,ArrayFire 的JIT引擎会将它们合并在一起。这种“内核融合”技术不仅减少了内核调用的数量,而且更重要的是,避免了外部的全局内存操作。我们的 JIT 功能扩展了 C/C++ 函数边界,并且只在遇到非JIT函数或代码显式调用同步操作时才结束。
ArrayFire 为元素操作提供了数百个函数。支持所有的标准运算符(例如+、-、*、/),也支持大多数超越函数(sin、cos、log、sqrt等)。下面是一些例子:
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
ArrayFire 包含一些与平台无关的常量,比如 Pi、NaN 和 Inf。如果 ArrayFire 没有你需要的常量,你可以使用 af::constant 数组构造函数创建自己的常量。
常量可以在 ArrayFire 的所有函数中使用。下面我们将演示它们在元素选择和数学表达式中的使用:
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));
请注意,我们的常量有时可能会与标准头文件中的宏定义冲突。当出现这种情况时,请使用 af:: 命名空间引用我们的常量。