Struct: keep direct reference to IMEMO/fields when space allows

It's not rare for structs to have additional ivars, hence are one
of the most common, if not the most common type in the `gen_fields_tbl`.

This can cause Ractor contention, but even in single ractor mode
means having to do a hash lookup to access the ivars, and increase
GC work.

Instead, unless the struct is perfectly right sized, we can store
a reference to the associated IMEMO/fields object right after the
last struct member.

```
compare-ruby: ruby 3.5.0dev (2025-08-06T12:50:36Z struct-ivar-fields-2 9a30d141a1) +PRISM [arm64-darwin24]
built-ruby: ruby 3.5.0dev (2025-08-06T12:57:59Z struct-ivar-fields-2 2ff3ec237f) +PRISM [arm64-darwin24]
warming up.....

|                      |compare-ruby|built-ruby|
|:---------------------|-----------:|---------:|
|member_reader         |    590.317k|  579.246k|
|                      |       1.02x|         -|
|member_writer         |    543.963k|  527.104k|
|                      |       1.03x|         -|
|member_reader_method  |    213.540k|  213.004k|
|                      |       1.00x|         -|
|member_writer_method  |    192.657k|  191.491k|
|                      |       1.01x|         -|
|ivar_reader           |    403.993k|  569.915k|
|                      |           -|     1.41x|
```

Co-Authored-By: Étienne Barrié <etienne.barrie@gmail.com>
This commit is contained in:
Jean Boussier 2025-08-06 14:46:36 +02:00
parent 9b3ad3449b
commit f3206cc79b
8 changed files with 194 additions and 23 deletions

View file

@ -11,10 +11,23 @@
#include "ruby/internal/stdbool.h" /* for bool */
#include "ruby/ruby.h" /* for struct RBasic */
/* Flags of RStruct
*
* 1-7: RSTRUCT_EMBED_LEN
* If non-zero, the struct is embedded (its contents follow the
* header, rather than being on a separately allocated buffer) and
* these bits are the length of the Struct.
* 8: RSTRUCT_GEN_FIELDS
* The struct is embedded and has no space left to store the
* IMEMO/fields reference. Any ivar this struct may have will be in
* the generic_fields_tbl. This flag doesn't imply the struct has
* ivars.
*/
enum {
RSTRUCT_EMBED_LEN_MASK = RUBY_FL_USER7 | RUBY_FL_USER6 | RUBY_FL_USER5 | RUBY_FL_USER4 |
RUBY_FL_USER3 | RUBY_FL_USER2 | RUBY_FL_USER1,
RSTRUCT_EMBED_LEN_SHIFT = (RUBY_FL_USHIFT+1),
RSTRUCT_GEN_FIELDS = RUBY_FL_USER8,
};
struct RStruct {
@ -23,6 +36,7 @@ struct RStruct {
struct {
long len;
const VALUE *ptr;
VALUE fields_obj;
} heap;
/* This is a length 1 array because:
* 1. GCC has a bug that does not optimize C flexible array members
@ -116,4 +130,31 @@ RSTRUCT_GET(VALUE st, long k)
return RSTRUCT_CONST_PTR(st)[k];
}
static inline VALUE
RSTRUCT_FIELDS_OBJ(VALUE st)
{
const long embed_len = RSTRUCT_EMBED_LEN(st);
VALUE fields_obj;
if (embed_len) {
RUBY_ASSERT(!FL_TEST_RAW(st, RSTRUCT_GEN_FIELDS));
fields_obj = RSTRUCT_GET(st, embed_len);
}
else {
fields_obj = RSTRUCT(st)->as.heap.fields_obj;
}
return fields_obj;
}
static inline void
RSTRUCT_SET_FIELDS_OBJ(VALUE st, VALUE fields_obj)
{
const long embed_len = RSTRUCT_EMBED_LEN(st);
if (embed_len) {
RUBY_ASSERT(!FL_TEST_RAW(st, RSTRUCT_GEN_FIELDS));
RSTRUCT_SET(st, embed_len, fields_obj);
}
else {
RB_OBJ_WRITE(st, &RSTRUCT(st)->as.heap.fields_obj, fields_obj);
}
}
#endif /* INTERNAL_STRUCT_H */