Ruby (2.0) c 扩展 return 来自 c 函数的数组数组

Ruby (2.0) c extension return array of arrays from c function

我正在编写一个 Ruby c 扩展来使用 Accelerate macos 框架执行线性代数计算。为了使用所有可用的内核,我还使用 rb_thread_call_without_gvl 来解锁全局 VM 锁。

我不是 C 专家所以请耐心等待。

想法是创建一个 ruby 对两个输入矩阵进行操作的方法

VALUE matmat_mul(VALUE self, VALUE matrixA, VALUE matrixB)

在这种方法中,我创建了一个 struct,然后将其传递给实际函数

void* matmat_mul_nogvl(void* inputMatricesPtr)

您可以从输出中看到,在 c 代码中一切都按预期工作,但我很难理解如何 return 从 c 代码返回最终矩阵(数组的数组)。最终矩阵为nil。我认为我缺少将 matC 转换回 Ruby 对象的部分。

这是我到目前为止的代码(有很多调试 printf 来验证计算是否正常工作)

#include <stdio.h>
#include <ruby.h>
#include <ruby/thread.h>
#include <time.h>
#include <Accelerate/Accelerate.h>
#include <math.h>

typedef struct {
    double *matrix;
    int nrows;
    int ncols;
}Matrix;

typedef struct {
    Matrix A;
    Matrix B;
    Matrix C;
}Transfer;


void* matmat_mul_nogvl(void* inputMatricesPtr)
{
    printf("The input matrix struct is at the address %p\n", inputMatricesPtr);
    int i,j;
    int cblas_order = 101;
    int cblas_transpose = 111;
    Transfer inputMatrices;
    inputMatrices = *(Transfer *)inputMatricesPtr;
    double *matA = inputMatrices.A.matrix;
    int rowsA = inputMatrices.A.nrows;
    int colsA = inputMatrices.A.ncols;
    double *matB = inputMatrices.B.matrix;
    int rowsB = inputMatrices.B.nrows;
    int colsB = inputMatrices.B.ncols;
    double *matC = inputMatrices.C.matrix;
    int rowsC = inputMatrices.C.nrows;
    int colsC = inputMatrices.C.ncols;

    printf("\nIn cblas_dgem\n");
    time_t t = time(NULL);
    struct tm tm = *localtime(&t);
    printf("%d-%02d-%d %02d:%02d:%02d - In cblas_dgem\n", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
    printf("Matrix A shape: (%d,%d)\n", rowsA, colsA);
    printf("Matrix B shape: (%d,%d)\n", rowsB, colsB);
    printf("Matrix C shape: (%d,%d)\n\n", rowsC, colsC);



    int lda = colsA;
    int ldb = colsB;
    int ldc = colsC;
    cblas_dgemm(cblas_order, cblas_transpose, cblas_transpose, rowsA, colsB, colsA, 1.0, matA, lda, matB, ldb, 1.0, matC, ldc);

    for (i=0; i<rowsA; i++)
    {
        for (j=0; j<colsA; j++)
        {
            printf("Matrix A Element(%d,%d)=%f\n", i, j, matA[i * colsA + j]);
        }
    }
    for (i=0; i<rowsB; i++)
    {
        for (j=0; j<colsB; j++)
        {
            printf("Matrix B Element(%d,%d)=%f\n", i, j, matB[i * colsB + j]);
        }
    }

    for (i=0; i<rowsC; i++)
    {
        for (j=0; j<colsC; j++)
        {
            printf("Matrix C Element(%d,%d)=%f\n", i, j, matC[i * colsC + j]);
        }
    }




    return NULL;
}


VALUE matmat_mul(VALUE self, VALUE matrixA, VALUE matrixB)
{
    printf("\nIn matmul\n");
    time_t t = time(NULL);
    struct tm tm = *localtime(&t);
    printf("%d-%02d-%d %02d:%02d:%02d In matmul\n", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);


    int rowsA = RARRAY_LEN(matrixA);
    VALUE firstElement = rb_ary_entry(matrixA, 0);
    int colsA = RARRAY_LEN(firstElement);
    printf("Matrix A shape: (%d,%d)\n", rowsA, colsA);
    int rowsB = RARRAY_LEN(matrixB);
    firstElement = rb_ary_entry(matrixB, 0);
    int colsB = RARRAY_LEN(firstElement);
    printf("Matrix B shape: (%d,%d)\n", rowsB, colsB);

    int i,j;
    double *matA = (double *)malloc(rowsA * colsA * sizeof(double));
    double *matB = (double *)malloc(rowsB * colsB * sizeof(double));
    int rowsC = rowsA;
    int colsC = colsB;
    printf("Matrix C shape: (%d,%d)\n\n", rowsC, colsC);
    double *matC = (double *)malloc(rowsC * colsC * sizeof(double));



    VALUE rowA;
    for (i=0; i<rowsA; i++)
    {
        rowA = rb_ary_entry(matrixA, i);
        for (j=0; j<colsA; j++)
        {
            matA[i * colsA + j] = NUM2DBL(rb_ary_entry( rowA, j));
            printf("Matrix A Element(%d,%d)=%f\n", i, j, matA[i * colsA + j]);
        }
    }
    printf("\n");

    VALUE rowB;
    for (i=0; i<rowsB; i++)
    {
        rowB = rb_ary_entry(matrixB, i);
        for (j=0; j<colsB; j++)
        {
            matB[i * colsB + j] = NUM2DBL(rb_ary_entry( rowB, j));
            printf("Matrix B Element(%d,%d)=%f\n", i, j, matB[i * colsB + j]);
        }
    }
    printf("\nBefore MatMul Matrix C is:\n");
    for (i=0; i<rowsC; i++)
    {
        for (j=0; j<colsC; j++)
        {
            matC[i * colsC + j] = 0.0;
            printf("Matrix C Element(%d,%d)=%f\n", i, j, matC[i * colsC + j]);
        }
    }


    printf("\n");


    Matrix inputMatrixA = {matA, rowsA, colsA};
    Matrix inputMatrixB = {matB, rowsB, colsB};
    Matrix inputMatrixC = {matC, rowsC, colsC};

    Transfer inputMatrices = {inputMatrixA, inputMatrixB, inputMatrixC};

    rb_thread_call_without_gvl(matmat_mul_nogvl, &inputMatrices, NULL, NULL);

    printf("\nBack in MatMul Matrix C is:\n");
    for (i=0; i<rowsC; i++)
    {
        for (j=0; j<colsC; j++)
        {
            printf("Matrix C Element(%d,%d)=%f\n", i, j, matC[i * colsC + j]);
        }
    }

    free(matA);
    free(matB);
    return Qnil;
}

void Init_blasnogvl()
{
    VALUE rg = rb_define_module("RG");
    VALUE linalg = rb_define_module_under(rg, "LinearAlgebra");
    VALUE operation = rb_define_class_under(linalg, "Operation", rb_cObject);
    rb_define_method(operation, "matmat_mul", matmat_mul, 2);
}

你可以使用下面的方法编译它extconf.rb

require 'mkmf'
extension_name = 'blasnogvl'
have_framework('Accelerate')
create_makefile(extension_name)

并使用以下 ruby 代码进行测试

require './blasnogvl'

puts "#{Time.now} - Started"

rows = 4
cols = 3
mat = Array.new(rows){Array.new(cols){rand}}
puts "#{Time.now} - Matrix generated"
mat[0] = [0.0, 1.0, 2.0]
mat[1] = [3.0, 4.0, 5.0]
mat[2] = [6.0, 7.0, 8.0]
mat[3] = [6.0, 7.0, 8.0]
puts mat.to_s

matA = mat
matB = mat.transpose
operation = RG::LinearAlgebra::Operation.new
matC = operation.matmat_mul(matA, matB)

puts "After calculation matA is"
puts matA.to_s

puts "After calculation matB is"
puts matB.to_s

puts "matC in ruby is"
puts matC.to_s
puts "#{Time.now} - Matrix calculated"

你的 matmat_mul 函数调用的最终值 return Qnil,这基本上是 ruby 中的 return nil

因此您需要为矩阵创建 ruby 数组,创建一个数组来保存每一行数据,填充这些行并将它们推入结果数组。

首先可以通过 rb_ary_new 实现,但由于维度已知,我们可以给 ruby 提示其大小将使用 rb_ary_new_capa,然后填充值rb_ary_pushArray#<< 方法的实现。

应执行以下代码:

// Multiplication code above ...
VALUE matrixC = rb_ary_new_capa(rowsC);

for (i = 0; i < rowsC; i++) {
    VALUE rowC = rb_ary_new_capa(colsC);

    for (j = 0; j < colsC; j++)
        rb_ary_push(rowC, DBL2NUM(matC[i * colsC + j]);

    rb_ary_push(matrixC, rowC);
}

return matrixC;