Use Set instead of Hash for Enumerable#uniq

This commit changes Enumerable#uniq to use a temporary Set instead of Hash
for performance and memory savings. We can see in the following benchmark:

    a = ((0...10_000).to_a + (0...10_000).to_a).each
    1_000.times { a.uniq }

Results:

    Benchmark 1: master
      Time (mean ± σ):     222.4 ms ±   3.7 ms    [User: 212.4 ms, System: 7.6 ms]
      Range (min … max):   216.9 ms … 227.6 ms    13 runs

    Benchmark 2: branch
      Time (mean ± σ):     189.7 ms ±   4.0 ms    [User: 177.7 ms, System: 9.2 ms]
      Range (min … max):   182.8 ms … 198.6 ms    15 runs

    Summary
      branch ran
        1.17 ± 0.03 times faster than master
This commit is contained in:
Peter Zhu committed 2026-09-13 14:34:33 +09:00
1 parent 7ebf7492a9
commit 29c669bb56
1 file changed
+25 -13
Notes: git 2026-09-13 05:35:02 +00:00
+25 -13
View File
@@ -20,6 +20,7 @@
#include "internal/proc.h"
#include "internal/rational.h"
#include "internal/re.h"
#include "internal/set.h"
#include "ruby/util.h"
#include "ruby_assert.h"
#include "symbol.h"
@@ -4870,18 +4871,26 @@ enum_sum(int argc, VALUE* argv, VALUE obj)
}
static VALUE
uniq_func(RB_BLOCK_CALL_FUNC_ARGLIST(i, hash))
uniq_func(RB_BLOCK_CALL_FUNC_ARGLIST(i, set))
{
ENUM_WANT_SVALUE();
rb_hash_add_new_element(hash, i, i);
rb_set_add_no_check(set, i);
return Qnil;
}
struct uniq_iter_memo {
VALUE set;
VALUE ary;
};
static VALUE
uniq_iter(RB_BLOCK_CALL_FUNC_ARGLIST(i, hash))
uniq_iter(RB_BLOCK_CALL_FUNC_ARGLIST(i, memo_))
{
struct uniq_iter_memo *memo = (struct uniq_iter_memo *)memo_;
ENUM_WANT_SVALUE();
rb_hash_add_new_element(hash, rb_yield_values2(argc, argv), i);
if (rb_set_add_no_check(memo->set, rb_yield_values2(argc, argv))) {
rb_ary_push(memo->ary, i);
}
return Qnil;
}
@@ -4909,15 +4918,18 @@ uniq_iter(RB_BLOCK_CALL_FUNC_ARGLIST(i, hash))
static VALUE
enum_uniq(VALUE obj)
{
VALUE hash, ret;
rb_block_call_func *const func =
rb_block_given_p() ? uniq_iter : uniq_func;
hash = rb_obj_hide(rb_hash_new());
rb_block_call(obj, id_each, 0, 0, func, hash);
ret = rb_hash_values(hash);
rb_hash_clear(hash);
return ret;
if (rb_block_given_p()) {
struct uniq_iter_memo memo;
memo.set = rb_obj_hide(rb_set_new());
memo.ary = rb_ary_new();
rb_block_call(obj, id_each, 0, 0, uniq_iter, (VALUE)&memo);
return memo.ary;
}
else {
VALUE set = rb_obj_hide(rb_set_new());
rb_block_call(obj, id_each, 0, 0, uniq_func, set);
return rb_set_to_a(set);
}
}
static VALUE