mruby-array-ext: implement fast path for uniq/uniq! in c

Co-authored-by: Gemini <gemini@google.com>
This commit is contained in:
Yukihiro "Matz" Matsumoto
2025-06-28 22:26:57 +09:00
parent 9e8cda73f6
commit ec7d94685d
2 changed files with 126 additions and 15 deletions
+19 -15
View File
@@ -16,24 +16,24 @@ class Array
# c.uniq! { |s| s.first } # => [["student", "sam"], ["teacher", "matz"]]
#
def uniq!(&block)
hash = {}
if block
hash = {}
result = []
self.each do |val|
key = block.call(val)
hash[key] = val unless hash.key?(key)
unless hash.key?(key)
hash[key] = true
result << val
end
end
result = hash.values
else
hash = {}
self.each do |val|
hash[val] = val
if result.size == self.size
nil
else
self.replace(result)
end
result = hash.keys
end
if result.size == self.size
nil
else
self.replace(result)
__uniq!
end
end
@@ -51,9 +51,13 @@ class Array
# b.uniq { |s| s.first } # => [["student", "sam"], ["teacher", "matz"]]
#
def uniq(&block)
ary = self[0..-1]
ary.uniq!(&block)
ary
if block
ary = self.dup
ary.uniq!(&block)
ary
else
__uniq
end
end
##
+107
View File
@@ -932,6 +932,111 @@ ary_fill_exec(mrb_state *mrb, mrb_value self)
return self;
}
/*
* Fast C implementation for Array#uniq without blocks.
* Uses hash-based deduplication for large arrays,
* linear search for small arrays.
*/
static mrb_value
ary_uniq(mrb_state *mrb, mrb_value self)
{
struct RArray *ary = mrb_ary_ptr(self);
mrb_int len = ARY_LEN(ary);
mrb_value *ptr = ARY_PTR(ary);
mrb_value result = mrb_ary_new_capa(mrb, len);
if (len == 0) {
return result;
}
if (len > SET_OP_HASH_THRESHOLD) {
mrb_value hash = mrb_hash_new_capa(mrb, len);
for (mrb_int i = 0; i < len; i++) {
mrb_value elem = ptr[i];
if (mrb_nil_p(mrb_hash_get(mrb, hash, elem))) {
mrb_hash_set(mrb, hash, elem, mrb_true_value());
mrb_ary_push(mrb, result, elem);
}
}
}
else {
for (mrb_int i = 0; i < len; i++) {
mrb_value elem = ptr[i];
mrb_bool found = FALSE;
mrb_value *result_ptr = ARY_PTR(RARRAY(result));
for (mrb_int j = 0; j < RARRAY_LEN(result); j++) {
if (mrb_equal(mrb, elem, result_ptr[j])) {
found = TRUE;
break;
}
}
if (!found) {
mrb_ary_push(mrb, result, elem);
}
}
}
return result;
}
/*
* Fast C implementation for Array#uniq! without blocks.
* Modifies array in-place, returns nil if no changes.
*/
static mrb_value
ary_uniq_bang(mrb_state *mrb, mrb_value self)
{
struct RArray *ary = mrb_ary_ptr(self);
mrb_int len = ARY_LEN(ary);
if (len <= 1) {
return mrb_nil_value();
}
mrb_ary_modify(mrb, ary);
mrb_value *ptr = ARY_PTR(ary);
mrb_int write_pos = 0;
if (len > SET_OP_HASH_THRESHOLD) {
mrb_value hash = mrb_hash_new_capa(mrb, len);
for (mrb_int read_pos = 0; read_pos < len; read_pos++) {
mrb_value elem = ptr[read_pos];
if (mrb_nil_p(mrb_hash_get(mrb, hash, elem))) {
mrb_hash_set(mrb, hash, elem, mrb_true_value());
if (write_pos != read_pos) {
ptr[write_pos] = elem;
}
write_pos++;
}
}
}
else {
for (mrb_int read_pos = 0; read_pos < len; read_pos++) {
mrb_value elem = ptr[read_pos];
mrb_bool found = FALSE;
for (mrb_int j = 0; j < write_pos; j++) {
if (mrb_equal(mrb, elem, ptr[j])) {
found = TRUE;
break;
}
}
if (!found) {
if (write_pos != read_pos) {
ptr[write_pos] = elem;
}
write_pos++;
}
}
}
if (write_pos == len) {
return mrb_nil_value();
}
mrb_ary_resize(mrb, self, write_pos);
return self;
}
void
mrb_mruby_array_ext_gem_init(mrb_state* mrb)
{
@@ -955,6 +1060,8 @@ mrb_mruby_array_ext_gem_init(mrb_state* mrb)
mrb_define_method_id(mrb, a, MRB_SYM_Q(intersect), ary_intersect_p, MRB_ARGS_REQ(1));
mrb_define_method_id(mrb, a, MRB_SYM(__fill_parse_arg), ary_fill_parse_arg, MRB_ARGS_ARG(0,4));
mrb_define_method_id(mrb, a, MRB_SYM(__fill_exec), ary_fill_exec, MRB_ARGS_REQ(3));
mrb_define_method_id(mrb, a, MRB_SYM(__uniq), ary_uniq, MRB_ARGS_NONE());
mrb_define_method_id(mrb, a, MRB_SYM_B(__uniq), ary_uniq_bang, MRB_ARGS_NONE());
}
void