3

私のCライブラリは、非常に大きなPOD構造体の配列を生成します。それをRuby側に渡す最も効率的な方法は何ですか?Ruby側では、値の生の配列で問題ありません。

私の現在のソリューションは、各要素とフィールドを別々に保存することで機能し、非常に低速です。プロファイリングは、この関数が平均データでプログラム時間の約15%を要し、計算部分でさえないことを示しました。

について読みましData_Wrap_Structたが、必要かどうかわかりません。rawvoid*を文字列に渡してから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配列にコピーします。Data_Wrap_Structを使用してC配列をラップし、それに直接作用するRubyコレクションクラスを作成することで、これを回避できます。

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);

これだけではあまり役に立ちません。この新しいクラスでいくつかのメソッドを定義する必要があります。[]例として、個々SPacketのsへのアクセスを許可するメソッドを作成できます。

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);

Data_Get_Structはマクロであり、ラップされていないを返さないという点で、使用法は少し奇妙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);

これには、フィールドのすべてのゲッターとセッターを作成するための少しの作業が必要になる場合があります。これは、使用方法によって異なります。ここでは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);

この例では、オブジェクトIDやメモリ管理などについては気にしませんでした。すべてが同じ配列に支えられているため、すべてが同じデータを共有する複数のオブジェクトを持つことができるため、これが使用に問題がないかどうかを検討する必要があります。また、あなたは私がmalloc編集したが、freedではないことに気づいたかもしれません。データアレイを「所有」しているのは誰かを特定し、メモリリークが発生しないようにする必要があります。Data_Wrap_Structオブジェクトがガベージコレクションされてメモリを解放するときに呼び出される関数を渡すことができます。

まだご覧になっていない場合は、Pickaxeの本にC拡張機能に関する優れた章があり、オンラインで入手できるようになりました。

于 2012-12-29T23:51:16.340 に答える