【问题标题】:Keeping a C array and a Ruby array in sync using an extension?使用扩展保持 C 数组和 Ruby 数组同步?
【发布时间】:2015-07-02 01:28:29
【问题描述】:

我正在用 Ruby 编写 C 扩展,但遇到了一些问题。我有几个 C 结构,这是一个简化版本:

typedef struct
{
  int score;
} Player;

typedef struct
{
  int numPlayers;
  Player *players;
} Game;

我将这些很好地封装在一个 C 扩展中。我已经为每个结构设置了方法,这是玩家的,因为这是我感兴趣的:

static VALUE players(VALUE self)
{
  Game *g;
  Data_Get_Struct(self, Game, g);
  VALUE players = rb_ary_new();
  for (int i = 0; i<g->numPlayers; i++)
  {
    //cPlayer is the ruby class I defined in my Init method
    VALUE player = Data_Wrap_Struct(cPlayer, NULL, NULL, &g->players[i]);
    rb_ary_push(players, player);
  }
  return players;
}

static VALUE set_players(VALUE self, VALUE players)
{
  Game *g;
  Data_Get_Struct(self, Game, g);
  free(g->players);
  g->players = malloc(sizeof(Player)*RARRAY_LEN(players));
  for (int i = 0; i<RARRAY_LEN(players); i++)
  {
    VALUE iValue = INT2NUM(i);
    VALUE player = rb_ary_aref(1, &iValue, players);
    Player *cPlayer;
    Data_Struct_Get(player, Player, cPlayer);
    memcpy(&g->players[i], cPlayer, sizeof(Player));
  }
  return Qnil;
}

然后,在我的 Ruby 代码中,我可以这样做:

g = Game.new
g.players = [Player.new, Player.new, Player.new]
g.players.each {|player| player.score = 5}

这很好用。但是,我不知道该怎么做:

g.players << Player.new
 => [<Player0>, <Player1>, <Player2>, <Player3>]
g.players
 => [<Player0>, <Player1>, <Player2>]
g.players[0] = Player.new
 => [<Player4>, <Player1>, <Player2>]
g.players
 => [<Player0>, <Player1>, <Player2>]

显然,问题是当我访问数组时,它每次都会计算一个新数组。因此,当我添加或追加一个播放器时,该数组会发生变化,但底层播放器数组保持不变。我觉得我必须有两个数组,一个在 Ruby 中,一个在 C 中。每当我将一些玩家添加到 C 数组中时,比如在 initialize_game 函数中,我必须更新 Ruby 数组,并且每当 Ruby 数组更新时,必须有某种回调来更新 C 数组。但是,我不确定该怎么做。

【问题讨论】:

  • 我实际上只是灵光一现,意识到这种设计模式可能是真正的问题,我应该创建纯 Ruby 对象,而不是包装 C 结构。然后,在我的扩展中,我需要提高性能的方法应该从当前的 Ruby 数组创建新的结构,然后使用这些新创建的结构作为输入运行更快的 C 函数。似乎比我一直在做的更容易和更易于维护。其他人同意这是要走的路吗?
  • 这取决于按需派生所需的结构并将结果写回 Ruby 对象的成本。您应该是“原生 Ruby,在 C 中转换/转换”或“原生 C,提供 Ruby 访问”。无论如何,调用 g.players[0] = Player.new 可能不是一个好方法,因为你的调用者对 Game 对象的干扰太深,无法做到这一点 - 它需要知道 Game 应该封装

标签: c ruby arrays


【解决方案1】:

您可以在结构中存储一个 Ruby VALUE 引用:

typedef struct
{
  int numPlayers;
  VALUE players;
} Game;

你需要单独初始化它(我通常实现一个init函数并在alloc例程之后调用它)。

此外,您必须在被问到时使用rb_gc_mark 标记值 - 您需要在Data_Wrap_Struct 中填写那些NULL 引用才能与Ruby 的GC 配合使用。

你真的应该已经提供一个销毁函数,否则你会出现内存泄漏:

例如

Data_Wrap_Struct( cGame, game_gc_mark, game_destroy, new_game );

game_gc_mark 应如下所示:

void game_gc_mark( Game *g ) {
  rb_gc_mark( g->players );
  return;
}

game_destroy 应如下所示:

void game_destroy( Game *g ) {
  xfree( g );
  return;
}

请注意,如果您这样做,则无需为数组内的 ArrayPlayer 对象处理内存管理 - Ruby 的 GC 将为您处理,只要您提供类似的方法来销毁 Player 对象.

这样做的好处是所有对象都可以像普通 Ruby 一样访问,而在幕后,数据存储在结构中,可以有效地用于您想要在 C 中实现的任何方法。没有来回转换,没有生成新的 Ruby 对象以遵循关系,只需在需要时加快速度并有时间编写 C。

缺点是您需要针对 Ruby 代码对 player 数组的操作进行防御性编码,否则如果您假设您有 Player 对象,则可能会出现段错误,但实际上一个错误导致它们成为其他类。

【讨论】:

  • 我担心 ruby​​ 数组的速度。最终的应用程序是一个相当密集的 Monte Carlo 模拟程序,我首先用 Ruby 编写了它(是的,这不是 Ruby 的强项),然后用 C 重写以立即实现两个数量级的加速。我刚刚编写了一个快速测试来循环遍历一个 int 数组和一个填充了 30 个整数的 Ruby 数组,这是它经常做的事情,并且它显示了相同的两个数量级的差异。我只需要 Ruby 部分来连接我的 Rails 应用程序以显示在网页上。
  • 在那种情况下,你为什么要关心g.players[0] = Player.new 的行为呢?那么如果g.players 不是直接可变的呢?
  • 另外,您不需要像这样让 Ruby 访问 C 中的 every 数组。 . .只是你想在 Ruby 中操作的主要关系。您需要快速迭代的数组可以保留为 ints 等的原生 C 数组。如果您真的需要在 C 中快速且在 Ruby 中对数值数组进行可变,请查看 NArray gem。我的 gem convolver 使用 NArray,它是一个很小的单一用途 gem,所以你可以看看它是如何工作的(注意它返回 new Narrays,但你可以按照我的将它们存储为 VALUE如果您在结构中需要它们,请回答)。
猜你喜欢
  • 1970-01-01
  • 2019-11-14
  • 2021-02-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-31
  • 1970-01-01
  • 2016-04-09
相关资源
最近更新 更多