This post covers how to create arrays using arrayfire-rb
, perform elementwise
operations like addition, multiplication, etc. and how these functions have been
implemented by creating Ruby bindings to ArrayFire C API.
ArrayFire for Ruby stores arrays upto 4 dimension in the Af_Array
class.
The speedup achieved is outstanding.
(Note: The above benchmarks have been done on an AMD FX 8350 octacore processor and Nvidia GTX 750Ti GPU.)
The figure shows that ArrayFire takes the least computation time of all. For elementwise arithmetic operations, ArrayFire is 2 e 4 times faster than NMatrix for Ruby whereas 2 e 3 times faster than NMatrix for JRuby.
The performance benchmarks for ArrayFire against NMatrix can be represented by the following figures. The code used for benchmarking and generating the plots can be found here and be used to reproduce similar plots.
Lets take a look at the implementation.
An Af_Array
expects the number of dimensions( ndims
), size of array along each
dimension(dimension
) and the elements(elements
).
Af_Array class is created under the ArrayFire module using rb_define_class_under()
.
Next, I have added arf_alloc
function using rb_define_alloc_func
that allocates
memory to Af_Array
and is run everytime Af_Array#new
is called. arf_alloc
works
along with arf_init
that accepts parameters from Ruby call and calls C to create
an af_array
. ArrayFire C apis use af_array
pointer to store an array.
The methods for elemetwise operations have also been implemented using rb_define_method
.
void Init_arrayfire() {
ArrayFire = rb_define_module("ArrayFire");
Af_Array = rb_define_class_under(ArrayFire, "Af_Array", rb_cObject);
rb_define_alloc_func(Af_Array, arf_alloc);
rb_define_method(Af_Array, "initialize", (METHOD)arf_init, -1);
rb_define_method(Af_Array, "+",(METHOD)arf_ew_add,1);
rb_define_method(Af_Array, "-",(METHOD)arf_ew_subtract,1);
rb_define_method(Af_Array, "*",(METHOD)arf_ew_multiply,1);
rb_define_method(Af_Array, "/",(METHOD)arf_ew_divide,1);
}
To store an Af_Array
, I have create an afstruct that stores the af_array
pointer.
arf_init(int argc, VALUE* argv, VALUE self)
can take any number of arguments and self
is used to bind the afstruct
to an Af_Array
object.
The Ruby C APIs uses VALUE
to pass around pointers. I have casted all the VALUE
types
to the C types expected by ArrayFire C API. NUM2LONG
and NUM2DBL
have been used to
convert the VALUE to long
and double
respectively.
Once, I have the ndims, dimensions and elements, I can use af_create_array
to create an
array. afstruct->carray
points to the array and the array can be accessed anytime by
using this pointer.
The point to note here is that all the data is on GPU now and hence, the time and resources
for copying data from GPU to CPU is taken care of. It may not be clear now but it will be
pivotal in the blogs to come when I interface mixed_models
with arrayfire.
typedef struct AF_STRUCT
{
af_array carray;
}afstruct;
VALUE arf_init(int argc, VALUE* argv, VALUE self)
{
afstruct* afarray;
Data_Get_Struct(self, afstruct, afarray);
dim_t ndims = (dim_t)NUM2LONG(argv[0]);
dim_t* dimensions = (dim_t*)malloc(ndims * sizeof(dim_t));
dim_t count = 1;
for (size_t index = 0; index < ndims; index++) {
dimensions[index] = (dim_t)NUM2LONG(RARRAY_AREF(argv[1], index));
count *= dimensions[index];
}
float* host_array = (float*)malloc(count * sizeof(float));
for (size_t index = 0; index < count; index++) {
host_array[index] = (float)NUM2DBL(RARRAY_AREF(argv[2], index));
}
af_create_array(&afarray->carray, host_array, ndims, dimensions, f32);
af_print_array(afarray->carray);
return self;
}
static VALUE arf_alloc(VALUE klass)
{
/* allocate */
afstruct* af = ALLOC(afstruct);
/* wrap */
return Data_Wrap_Struct(klass, NULL, arf_free, af);
}
So, now I can check if the bindings work successfully.
$ rake pry
pry -r './lib/arrayfire.rb'
[1] pry(main)> a = ArrayFire::Af_Array.new 2, [2,2],[1,2,3,4]
No Name Array
[2 2 1 1]
Offsets: [0 0 0 0]
Strides: [1 2 4 4]
1.0000 3.0000
2.0000 4.0000
=> #<ArrayFire::Af_Array:0x000000020aeab8>
[2] pry(main)>
(Note: ArrayFire stores array in column-major format.)
Voila! It works.
The following code snippets show how I implemented elemetwise operations like
addition
, subtraction
, multiplication
and division
.
I have created macros DEF_ELEMENTWISE_RUBY_ACCESSOR
and DECL_ELEMENTWISE_RUBY_ACCESSOR
that
define and declare the functions. The function names call the corresponding
ArrayFire API.
e.g. Af_Array#+
calls arf_ew_add
which is responsible for calling af_add
.
#define DEF_ELEMENTWISE_RUBY_ACCESSOR(name, oper) \
static VALUE arf_ew_##name(VALUE left_val, VALUE right_val) { \
afstruct* left; \
afstruct* right; \
afstruct* result = ALLOC(afstruct); \
Data_Get_Struct(left_val, afstruct, left); \
Data_Get_Struct(right_val, afstruct, right); \
af_##oper(&result->carray, left->carray, right->carray, true); \
af_print_array(result->carray); \
return Data_Wrap_Struct(CLASS_OF(left_val), NULL, arf_free, result); \
}
#define DECL_ELEMENTWISE_RUBY_ACCESSOR(name) \
static VALUE arf_ew_##name(VALUE left_val, VALUE right_val);
DECL_ELEMENTWISE_RUBY_ACCESSOR(add)
DECL_ELEMENTWISE_RUBY_ACCESSOR(subtract)
DECL_ELEMENTWISE_RUBY_ACCESSOR(multiply)
DECL_ELEMENTWISE_RUBY_ACCESSOR(divide)
DEF_ELEMENTWISE_RUBY_ACCESSOR(add, add)
DEF_ELEMENTWISE_RUBY_ACCESSOR(subtract, sub)
DEF_ELEMENTWISE_RUBY_ACCESSOR(multiply, mul)
DEF_ELEMENTWISE_RUBY_ACCESSOR(divide, div)
Now, we can check the elementwise operations using pry
.
$ rake pry
pry -r './lib/arrayfire.rb'
[1] pry(main)> a = ArrayFire::Af_Array.new 2, [2,2],[1,2,3,4]
No Name Array
[2 2 1 1]
Offsets: [0 0 0 0]
Strides: [1 2 4 4]
1.0000 3.0000
2.0000 4.0000
=> #<ArrayFire::Af_Array:0x000000020a3e38>
[2] pry(main)> b = a + a
No Name Array
[2 2 1 1]
Offsets: [0 0 0 0]
Strides: [1 2 4 4]
2.0000 6.0000
4.0000 8.0000
=> #<ArrayFire::Af_Array:0x000000020625c8>
[3] pry(main)> b = a * a
No Name Array
[2 2 1 1]
Offsets: [0 0 0 0]
Strides: [1 2 4 4]
1.0000 9.0000
4.0000 16.0000
=> #<ArrayFire::Af_Array:0x00000001fe6f90>
It works!
Lets check it for 4 dimensional matrices
$ rake pry
pry -r './lib/arrayfire.rb'
[1] pry(main)> a = ArrayFire::Af_Array.new 4, [2,2,2,2], [1,2,3,4,
5,6,7,8,
9,10,11,12,
13,14,15,16]
No Name Array
[2 2 2 2]
Offsets: [0 0 0 0]
Strides: [1 2 4 8]
1.0000 3.0000
2.0000 4.0000
5.0000 7.0000
6.0000 8.0000
9.0000 11.0000
10.0000 12.0000
13.0000 15.0000
14.0000 16.0000
=> #<ArrayFire::Af_Array:0x000000016c7a40>
[2] pry(main)> b = a + a
No Name Array
[2 2 2 2]
Offsets: [0 0 0 0]
Strides: [1 2 4 8]
2.0000 6.0000
4.0000 8.0000
10.0000 14.0000
12.0000 16.0000
18.0000 22.0000
20.0000 24.0000
26.0000 30.0000
28.0000 32.0000
=> #<ArrayFire::Af_Array:0x00000001686f18>
Hence, Af_Array can successfully handle arrays upto 4 dimesnions.
ArrayFire for Ruby can successfully create arrays on GPU using Af_Array
class and supports
elementwise binary operations. Similarly, I have implemented elementwise unary operations like Af_Array#sin
Af_Array#erfc
.
In the next blog post, I will explain about the test-suite and Algorithm class.
©2016-2024 Prasun Anand