mruby-array-ext: replace ruby hash with khash.h for set operations

Replace Ruby Hash usage in array set operations with khash.h for better
memory efficiency and performance. This affects operations on arrays
larger than 32 elements.

Changes:
- Add KHASH_DECLARE/DEFINE for ary_set_t (set mode)
- Replace mrb_hash_* calls with kh_* equivalents
- Add helper functions for temporary set management
- Update all affected functions:
  * ary_subtract_internal (difference operations)
  * ary_union_internal (union operations)
  * ary_intersection_internal (intersection operations)
  * ary_intersect_p (intersection checking)
  * ary_uniq_bang (uniqueness operations)

Benefits:
- Better performance: direct C operations vs Ruby method calls
- Consistent with mruby core architecture using khash.h
- Eliminates unnecessary mrb_true_value() storage

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Yukihiro "Matz" Matsumoto
2025-08-01 06:13:37 +09:00
parent 19f9675743
commit f9243aa67a
+95 -46
View File
@@ -6,6 +6,25 @@
#include <mruby/hash.h>
#include <mruby/internal.h>
#include <mruby/presym.h>
#include <mruby/khash.h>
/* khash set for temporary array operations */
static inline khint_t
ary_set_hash_func(mrb_state *mrb, mrb_value key)
{
return (khint_t)mrb_obj_hash_code(mrb, key);
}
static inline mrb_bool
ary_set_equal_func(mrb_state *mrb, mrb_value a, mrb_value b)
{
return mrb_eql(mrb, a, b);
}
KHASH_DECLARE(ary_set, mrb_value, char, 0)
KHASH_DEFINE(ary_set, mrb_value, char, 0, ary_set_hash_func, ary_set_equal_func)
typedef khash_t(ary_set) ary_set_t;
/*
* call-seq:
@@ -352,14 +371,31 @@ ary_rotate_bang(mrb_state *mrb, mrb_value self)
#define SET_OP_HASH_THRESHOLD 32
static void
ary_update_hash_set(mrb_state *mrb, mrb_value ary, mrb_value hash)
/* Helper functions for temporary khash sets */
static ary_set_t*
ary_create_temp_set(mrb_state *mrb, mrb_int capacity)
{
for (mrb_int i = 0; i < RARRAY_LEN(ary); i++) {
mrb_hash_set(mrb, hash, RARRAY_PTR(ary)[i], mrb_true_value());
return kh_init_size(ary_set, mrb, capacity > 0 ? capacity : 8);
}
static void
ary_populate_temp_set(mrb_state *mrb, ary_set_t *set, mrb_value ary)
{
mrb_int len = RARRAY_LEN(ary);
for (mrb_int i = 0; i < len; i++) {
kh_put(ary_set, mrb, set, RARRAY_PTR(ary)[i]);
}
}
static void
ary_destroy_temp_set(mrb_state *mrb, ary_set_t *set)
{
if (set) {
kh_destroy(ary_set, mrb, set);
}
}
static mrb_int
ary_get_array_args(mrb_state *mrb, mrb_int argc, const mrb_value **argv_ptr)
{
@@ -391,20 +427,22 @@ ary_subtract_internal(mrb_state *mrb, mrb_value self, mrb_int argc, const mrb_va
mrb_value result = mrb_ary_new(mrb);
if (total_len > SET_OP_HASH_THRESHOLD) {
mrb_value hash = mrb_hash_new_capa(mrb, total_len);
ary_set_t *set = ary_create_temp_set(mrb, total_len);
for (mrb_int i = 0; i < argc; i++) {
ary_update_hash_set(mrb, argv[i], hash);
ary_populate_temp_set(mrb, set, argv[i]);
}
mrb_int self_len = RARRAY_LEN(self);
for (mrb_int i = 0; i < self_len; i++) {
mrb_value p = RARRAY_PTR(self)[i];
mrb_value val = mrb_hash_get(mrb, hash, p);
if (mrb_nil_p(val)) { /* key doesn't exist in any ary */
khiter_t k = kh_get(ary_set, mrb, set, p);
if (k == kh_end(set)) { /* key doesn't exist in any ary */
mrb_ary_push(mrb, result, p);
}
}
ary_destroy_temp_set(mrb, set);
}
else {
mrb_int self_len = RARRAY_LEN(self);
@@ -467,18 +505,6 @@ ary_difference(mrb_state *mrb, mrb_value self)
return ary_subtract_internal(mrb, self, argc, argv);
}
static void
hash_update(mrb_state *mrb, mrb_value ary, mrb_value hash, mrb_value result)
{
const mrb_int len = RARRAY_LEN(ary);
for (mrb_int i = 0; i < len; i++) {
mrb_value val = mrb_hash_fetch(mrb, hash, RARRAY_PTR(ary)[i], mrb_undef_value());
if (mrb_undef_p(val)) { /* key doesn't exist */
mrb_hash_set(mrb, hash, RARRAY_PTR(ary)[i], mrb_true_value());
mrb_ary_push(mrb, result, RARRAY_PTR(ary)[i]);
}
}
}
static void
add_uniq(mrb_state *mrb, mrb_value item, mrb_value result)
@@ -500,15 +526,34 @@ ary_union_internal(mrb_state *mrb, mrb_value self, mrb_int argc, const mrb_value
mrb_value result = mrb_ary_new(mrb);
if (total_len > SET_OP_HASH_THRESHOLD) {
mrb_value hash = mrb_hash_new_capa(mrb, total_len);
ary_set_t *set = ary_create_temp_set(mrb, total_len);
/* Add elements from self */
hash_update(mrb, self, hash, result);
/* Add elements from others */
for (mrb_int i = 0; i < argc; i++) {
hash_update(mrb, argv[i], hash, result);
/* Add unique elements from self */
mrb_int alen = RARRAY_LEN(self);
for (mrb_int i = 0; i < alen; i++) {
mrb_value elem = RARRAY_PTR(self)[i];
khiter_t k = kh_get(ary_set, mrb, set, elem);
if (k == kh_end(set)) {
kh_put(ary_set, mrb, set, elem);
mrb_ary_push(mrb, result, elem);
}
}
/* Add unique elements from others */
for (mrb_int i = 0; i < argc; i++) {
mrb_value other = argv[i];
mrb_int olen = RARRAY_LEN(other);
for (mrb_int j = 0; j < olen; j++) {
mrb_value elem = RARRAY_PTR(other)[j];
khiter_t k = kh_get(ary_set, mrb, set, elem);
if (k == kh_end(set)) {
kh_put(ary_set, mrb, set, elem);
mrb_ary_push(mrb, result, elem);
}
}
}
ary_destroy_temp_set(mrb, set);
}
else {
/* Use linear search for small arrays */
@@ -581,21 +626,23 @@ ary_intersection_internal(mrb_state *mrb, mrb_value self, mrb_int argc, const mr
mrb_value result = mrb_ary_new(mrb);
if (total_len > SET_OP_HASH_THRESHOLD) {
mrb_value hash = mrb_hash_new_capa(mrb, total_len);
ary_set_t *set = ary_create_temp_set(mrb, total_len);
for (mrb_int i = 0; i < argc; i++) {
ary_update_hash_set(mrb, argv[i], hash);
ary_populate_temp_set(mrb, set, argv[i]);
}
mrb_int self_len = RARRAY_LEN(self);
for (mrb_int i = 0; i < self_len; i++) {
mrb_value p = RARRAY_PTR(self)[i];
mrb_value val = mrb_hash_get(mrb, hash, p);
if (!mrb_nil_p(val)) {
khiter_t k = kh_get(ary_set, mrb, set, p);
if (k != kh_end(set)) {
mrb_ary_push(mrb, result, p);
mrb_hash_delete_key(mrb, hash, p);
kh_del(ary_set, mrb, set, k);
}
}
ary_destroy_temp_set(mrb, set);
}
else {
mrb_int self_len = RARRAY_LEN(self);
@@ -674,13 +721,6 @@ ary_intersection_multi(mrb_state *mrb, mrb_value self)
}
static mrb_value
ary_to_hash_set(mrb_state *mrb, mrb_value ary)
{
mrb_value hash = mrb_hash_new_capa(mrb, RARRAY_LEN(ary));
ary_update_hash_set(mrb, ary, hash);
return hash;
}
/*
* call-seq:
@@ -717,14 +757,19 @@ ary_intersect_p(mrb_state *mrb, mrb_value self)
}
if (RARRAY_LEN(shorter_ary) > SET_OP_HASH_THRESHOLD) {
mrb_value hash = ary_to_hash_set(mrb, shorter_ary);
ary_set_t *set = ary_create_temp_set(mrb, RARRAY_LEN(shorter_ary));
ary_populate_temp_set(mrb, set, shorter_ary);
mrb_int longer_len = RARRAY_LEN(longer_ary);
for (mrb_int i = 0; i < longer_len; i++) {
mrb_value val = mrb_hash_get(mrb, hash, RARRAY_PTR(longer_ary)[i]);
if (!mrb_nil_p(val)) {
khiter_t k = kh_get(ary_set, mrb, set, RARRAY_PTR(longer_ary)[i]);
if (k != kh_end(set)) {
ary_destroy_temp_set(mrb, set);
return mrb_true_value();
}
}
ary_destroy_temp_set(mrb, set);
}
else {
mrb_int longer_len = RARRAY_LEN(longer_ary);
@@ -885,18 +930,22 @@ ary_uniq_bang(mrb_state *mrb, mrb_value self)
mrb_int write_pos = 0;
if (len > SET_OP_HASH_THRESHOLD) {
mrb_value hash = mrb_hash_new_capa(mrb, len);
ary_update_hash_set(mrb, self, hash);
ary_set_t *set = ary_create_temp_set(mrb, len);
ary_populate_temp_set(mrb, set, self);
for (mrb_int read_pos = 0; read_pos < len; read_pos++) {
mrb_value elem = RARRAY_PTR(self)[read_pos];
if (!mrb_nil_p(mrb_hash_get(mrb, hash, elem))) {
khiter_t k = kh_get(ary_set, mrb, set, elem);
if (k != kh_end(set)) {
if (write_pos != read_pos) {
RARRAY_PTR(self)[write_pos] = elem;
}
write_pos++;
mrb_hash_delete_key(mrb, hash, elem);
kh_del(ary_set, mrb, set, k);
}
}
ary_destroy_temp_set(mrb, set);
}
else {
for (mrb_int read_pos = 0; read_pos < len; read_pos++) {