3

我的 C 库生成了一个非常大的 POD 结构数组。将它传递给 Ruby 端的最有效方法是什么?在 Ruby 方面,原始值数组对我来说很好。

我当前的解决方案通过分别存储每个元素和字段来工作,而且速度非常慢。分析表明,这个函数在平均数据上占用大约 15% 的程序时间,它甚至不是计算部分。

我已经阅读过Data_Wrap_Struct,但不确定我是否需要它。如果我将一个 raw 传递void*给字符串,然后在 Ruby 端解压它,它会更快吗?

struct SPacket
{
    uint32_t field1;
    uint32_t field2;
    uint16_t field3;
    uint8_t field4;
};

VALUE rb_GetAllData(VALUE self) // SLOOOW
{
    size_t count = 0;

    struct SPacket* packets = GetAllData(&count);

    VALUE arr = rb_ary_new2(count);

    for(size_t i = 0; i < count; i++)
    {
        VALUE sub_arr = rb_ary_new2(4);

        rb_ary_store(sub_arr, 0, UINT2NUM(packets[i].field1));
        rb_ary_store(sub_arr, 1, UINT2NUM(packets[i].field2));
        rb_ary_store(sub_arr, 2, UINT2NUM(packets[i].field3));
        rb_ary_store(sub_arr, 3, UINT2NUM(packets[i].field4));

        rb_ary_store(arr, i, sub_arr);
    }

    return arr;
}
4

1 回答 1

3

您的方法将 C 数组复制到 Ruby 数组中。您可以通过创建一个 Ruby 集合类来避免这种情况,该集合类使用Data_Wrap_Struct并直接作用于 C 数组。

Data_Wrap_Struct是一个宏,它接受一个 Ruby 类和一个 C struct(以及可选的几个指向我故意省略的内存管理函数的指针)并创建一个具有struct“附加”的类的实例。在提供此类方法实现的函数中,您可以使用Data_Get_Struct“解包”这些方法,然后struct您可以在函数中访问这些方法。

在这种情况下,是这样的:

// declare a variable for the new class
VALUE rb_cSPacketCollection;

// a struct that will be wrapped by the class
struct SPacketDataStruct {
    struct SPacket * data;
    int count;
};

VALUE rb_GetAllData() {
    struct SPacketDataStruct* wrapper = malloc(sizeof (struct SPacketCollectionWrapper));
    wrapper->data = GetAllData(&wrapper->count);
    return Data_Wrap_Struct(rb_cSPacketCollection, 0, 0, wrapper);
}

在您的Init_whatever()方法中,您需要创建类:

rb_cSPacketCollection = rb_define_class("SPacketCollection", rb_cObject);

仅此一点用处不大,您需要在这个新类上定义一些方法。例如,您可以创建一个[]方法来允许访问单个SPackets:

VALUE SPacketCollection_get(VALUE self, VALUE index) {
    // unwrap the struct
    struct SPacketDataStruct* wrapper;
    Data_Get_Struct(self, struct SPacketDataStruct, wrapper);

    int i = NUM2INT(index);

    // bounds check
    if (i >= wrapper->count) {
        rb_raise(rb_eIndexError, "Index out of bounds");
    }

    // just return an array in this example
    VALUE arr = rb_ary_new2(4);

    rb_ary_store(arr, 0, UINT2NUM(wrapper->data[i].field1));
    rb_ary_store(arr, 1, UINT2NUM(wrapper->data[i].field2));
    rb_ary_store(arr, 2, UINT2NUM(wrapper->data[i].field3));
    rb_ary_store(arr, 3, UINT2NUM(wrapper->data[i].field4));

    return arr;
}

然后在您的Init_方法中,在创建类之后定义方法:

rb_define_method(rb_cSPacketCollection, "[]", SPacketCollection_get, 1);

NoteData_Get_Struct是一个宏,用法有点奇怪,因为它不返回 unwrapped struct

由于您已经在Data_Wrap_Struct这个阶段开始使用,您可以更进一步并创建一个新类,该类包装一个单独的SPacket结构并直接对其进行操作:

// declare a variable for the new class
VALUE rb_cSPacket;

//and a function to get a field value
// you'll need to create more methods to access the other fields
// (and possibly to set them)
VALUE SPacket_field1(VALUE self) {
    struct SPacket* packet;
    Data_Get_Struct(self, struct SPacket, packet);

    return UINT2NUM(packet->field1);
}

在您的Init_函数中,创建它并定义方法:

rb_cSPacket = rb_define_class("SPacket", rb_cObject);
rb_define_method(rb_cSPacket, "field1", SPacket_field1, 0);

这可能需要一些工作来为字段创建所有 getter 和 setter,这取决于您如何使用它。ffi 之类的东西在这里可以提供帮助,但我不知道 ffi 将如何处理集合类——它可能值得研究。

现在更改您的[]函数以返回一个实例,如果这个新类:

VALUE SPacketCollection_get(VALUE self, VALUE index) {
    //unwrap the struct
    struct SPacketDataStruct* wrapper;
    Data_Get_Struct(self, struct SPacketDataStruct, wrapper);

    int i = NUM2INT(index);

    //bounds check
    if (i >= wrapper->count) {
        rb_raise(rb_eIndexError, "Index out of bounds");
    }

    //create an instance of the new class, and wrap it around the 
    //struct in the array
    struct SPacket* packet = &wrapper->data[i];
    return Data_Wrap_Struct(rb_cSPacket, 0, 0, packet);
}

有了这个,你现在可以在 Ruby 中做这样的事情:

c = get_all_data # in my testing I just made this a global method
c[2].field1 # returns the value of field1 of the third SPacket in the array

在集合类上创建一个方法可能是值得的each,然后您可以包含该Enumerable模块并提供一系列方法:

VALUE SPacketCollection_each(VALUE self) {
    //unwrap the struct as before
    struct SPacketDataStruct* wrapper;
    Data_Get_Struct(self, struct SPacketDataStruct, wrapper);
    int i;

    for(i = 0; i < wrapper->count; i++) {
        //create a new instance if the SPacket class
        // wrapping this entry
        struct SPacket* packet = &wrapper->data[i];
        rb_yield(Data_Wrap_Struct(rb_cSPacket, 0, 0, packet));
    }
    return self;
}

Init_whatever

rb_define_method(rb_cSPacketCollection, "each", SPacketCollection_each, 0);
rb_include_module(rb_cSPacketCollection, rb_mEnumerable);

在这个例子中,我没有关心对象标识和内存管理之类的事情。使用同一个数组支持的所有内容,您可以拥有多个共享相同数据的对象,您必须考虑这是否适合您的使用。此外,您可能已经注意到我已经malloced 但不是freed。您需要确定谁“拥有”数据数组,并确保不会引入任何内存泄漏。您可以传递一个函数Data_Wrap_Struct,当对象被垃圾收集以释放内存时将调用该函数。

如果您还没有看过,Pickaxe 书中有一章很好地介绍了 C 扩展,现在可以在线获得

于 2012-12-29T23:51:16.340 回答