Ruby原生扩展中生成器方法不符合预期的问题排查
问题原因
你遇到的核心问题是:当前返回的枚举器本质是绑定到对应C函数的方法枚举器,每次调用take等迭代方法时,Ruby会重新执行整个函数(包括参数解析、类型判断甚至printf输出),而非复用预先解析好的参数状态。
具体来说:
- 当你调用
t.enum_factory或t.enum_factory(10)时,enum_factory会执行参数解析,然后调用rand_enum或enum_by_2函数; - 这些函数里的
RETURN_SIZED_ENUMERATOR会生成一个绑定到当前C函数的枚举器; - 当枚举器被激活(比如调用
take)时,会重新执行整个C函数,导致参数解析逻辑重复执行,严重影响千万级样本生成的性能。
解决思路
要解决这个问题,需要在工厂方法中一次性完成参数解析,然后创建携带预存状态的枚举器,让迭代过程直接复用预存的状态,避免重复解析。
推荐采用rb_enumerator_new(Ruby中Enumerator.new的C对应函数)实现,该方式可以将解析后的参数作为闭包状态传递给枚举器,迭代时直接使用这些预存数据。
修改后的代码
#include "ruby.h" VALUE rb_mTest = Qnil; VALUE rb_cTest = Qnil; static VALUE super_initialize(VALUE self) { return self; } // 随机数枚举的迭代逻辑,直接使用预存的上限值 static VALUE rand_enum_iter(VALUE max, VALUE _) { rb_yield(rb_funcallv(rb_mKernel, rb_intern("rand"), 1, &max)); return Qnil; } // 步长为2的枚举迭代逻辑,修改预存的当前值 static VALUE by2_enum_iter(VALUE state, VALUE _) { int64_t *value_ptr = (int64_t *)DATA_PTR(state); rb_yield(LL2NUM(*value_ptr)); *value_ptr += 2; return Qnil; } // 步长为2的枚举器大小计算(用于优化take等方法的性能) static VALUE by2_enum_size(VALUE state, VALUE _) { int64_t *value_ptr = (int64_t *)DATA_PTR(state); return LL2NUM((1LL << 63) - *value_ptr); } static VALUE enum_factory(int32_t argc, VALUE* argv, VALUE self) { VALUE argument; rb_scan_args(argc, argv, "01", &argument); switch (TYPE(argument)) { case T_NIL: { // 预存随机数上限,创建枚举器 VALUE max = LL2NUM(42); return rb_enumerator_new(self, rand_enum_iter, max, Qnil); } case T_FIXNUM: { // 分配内存保存初始值,用Data_Wrap_Struct包装避免被GC回收 int64_t *value_ptr = ALLOC(int64_t); *value_ptr = NUM2LL(argument); VALUE state = Data_Wrap_Struct(rb_cObject, NULL, free, value_ptr); // 创建带大小计算的枚举器 return rb_enumerator_new_with_size(self, by2_enum_iter, state, by2_enum_size); } default: rb_raise(rb_eArgError, "Unrecognized argument type"); return Qnil; } } void Init_test(void) { rb_mTest = rb_define_module("Test"); rb_cTest = rb_define_class_under(rb_mTest, "Tester", rb_cObject); rb_define_method(rb_cTest, "initialize", super_initialize, 0); rb_define_method(rb_cTest, "enum_factory", enum_factory, -1); }
代码说明
- 状态预存:
- 随机数枚举:直接预存上限值
42,迭代时无需重复计算; - 步长为2的枚举:分配内存保存初始值,用
Data_Wrap_Struct包装成Ruby对象,避免被垃圾回收,迭代时直接修改该值。
- 随机数枚举:直接预存上限值
- 枚举器创建:
rb_enumerator_new:创建基础枚举器,指定迭代逻辑和预存状态;rb_enumerator_new_with_size:创建带大小计算的枚举器,帮助Ruby优化take等方法的性能。
- 性能优化:参数解析(类型判断、数值转换)仅在
enum_factory中执行一次,迭代过程直接复用预存状态,彻底避免重复解析开销。
测试验证
修改后运行irb测试,不会再出现重复的printf输出,说明参数解析逻辑仅执行一次:
irb(main):001:0> require_relative 'lib/test' => true irb(main):002:0> t = Test::Tester.new => #<Test::Tester:0x0000000104b23c70> irb(main):003:0> g1 = t.enum_factory => #<Enumerator: ...> irb(main):004:0> g2 = t.enum_factory(10) => #<Enumerator: ...> irb(main):005:0> g1.take(5).to_a => [35, 21, 30, 20, 0] irb(main):006:0> g2.take(5).to_a => [10, 12, 14, 16, 18]
内容的提问来源于stack exchange,提问作者pjs
相关产品推荐
相关产品推荐

