diff options
author | Jeremy Evans <[email protected]> | 2025-04-27 17:23:04 -0700 |
---|---|---|
committer | Jeremy Evans <[email protected]> | 2025-05-05 09:46:32 +0900 |
commit | ce51ef30df5bf07ec3881a377f0011b8f20ec507 (patch) | |
tree | 27878fa2e8e428bcb43ca685f6904627fedc6080 /include/ruby | |
parent | 35918df740018a510d0f9434e6eca2a2ad533003 (diff) |
Save one VALUE per embedded RTypedData
This halves the amount of memory used for embedded RTypedData if they
are one VALUE (8 bytes on 64-bit platforms) over the slot size limit.
For Set, on 64-bit it uses an embedded 56-byte struct. With the
previous implementation, the embedded structs starts at offset 32,
resulting in a total size of 88. Since that is over the 80 byte
limit, it goes to the next highest bucket, 160 bytes, wasting 72
bytes. This allows it to fit in a 80 byte bucket, which reduces
the total size for small sets of from 224 bytes (160 bytes
embedded, 64 bytes malloc, 72 bytes wasted in embedding) to 144
bytes (80 bytes embedded, 64 bytes malloc, 0 bytes wasted in
embedding).
Any other embedded RTypedData will see similar advantages if they
are currently one VALUE over the limit.
To implement this, remove the typed_flag from struct RTypedData.
Embed the typed_flag information in the type member, which is
now a tagged pointer using VALUE type, using the bottom low 2 bits
as flags (1 bit for typed flag, the other for the embedded flag).
To get the actual pointer, RTYPEDDATA_TYPE masks out
the low 2 bits and then casts. That moves the RTypedData data
pointer from offset 32 to offset 24 (on 64-bit).
Vast amount of code in the internals (and probably external C
extensions) expects the following code to work for both RData and
non-embedded RTypedData:
```c
DATA_PTR(obj) = some_pointer;
```
Allow this to work by moving the data pointer in RData between
the dmark and dfree pointers, so it is at the same offset (24
on 64-bit).
Other than these changes to the include files, the only changes
needed were to gc.c, to account for the new struct layouts,
handle setting the low bits in the type member, and to use
RTYPEDDATA_TYPE(obj) instead of RTYPEDDATA(obj)->type.
Notes
Notes:
Merged: https://github.com/ruby/ruby/pull/13190
Diffstat (limited to 'include/ruby')
-rw-r--r-- | include/ruby/internal/core/rdata.h | 9 | ||||
-rw-r--r-- | include/ruby/internal/core/rtypeddata.h | 26 |
2 files changed, 19 insertions, 16 deletions
diff --git a/include/ruby/internal/core/rdata.h b/include/ruby/internal/core/rdata.h index e4c146a716..cab412af72 100644 --- a/include/ruby/internal/core/rdata.h +++ b/include/ruby/internal/core/rdata.h @@ -133,6 +133,12 @@ struct RData { */ RUBY_DATA_FUNC dmark; + /** Pointer to the actual C level struct that you want to wrap. + * This is in between dmark and dfree to allow DATA_PTR to continue + * to work for both RData and non-embedded RTypedData. + */ + void *data; + /** * This function is called when the object is no longer used. You need to * do whatever necessary to avoid memory leaks. @@ -141,9 +147,6 @@ struct RData { * impossible at that moment (that is why GC runs). */ RUBY_DATA_FUNC dfree; - - /** Pointer to the actual C level struct that you want to wrap. */ - void *data; }; RBIMPL_SYMBOL_EXPORT_BEGIN() diff --git a/include/ruby/internal/core/rtypeddata.h b/include/ruby/internal/core/rtypeddata.h index 6c19576c20..b576be779f 100644 --- a/include/ruby/internal/core/rtypeddata.h +++ b/include/ruby/internal/core/rtypeddata.h @@ -114,7 +114,10 @@ #define RUBY_TYPED_PROMOTED1 RUBY_TYPED_PROMOTED1 /** @endcond */ -#define TYPED_DATA_EMBEDDED 2 +#define IS_TYPED_DATA ((VALUE)1) +#define TYPED_DATA_EMBEDDED ((VALUE)2) +#define TYPED_DATA_PTR_FLAGS ((VALUE)3) +#define TYPED_DATA_PTR_MASK (~TYPED_DATA_PTR_FLAGS) /** * @private @@ -353,18 +356,16 @@ struct RTypedData { struct RBasic basic; /** + * This is a `const rb_data_type_t *const` value, with the low bits set: + * + * 1: Always set, to differentiate RTypedData from RData. + * 2: Set if object is embedded. + * * This field stores various information about how Ruby should handle a * data. This roughly resembles a Ruby level class (apart from method * definition etc.) */ - const rb_data_type_t *const type; - - /** - * This has to be always 1. - * - * @internal - */ - const VALUE typed_flag; + const VALUE type; /** Pointer to the actual C level struct that you want to wrap. */ void *data; @@ -525,7 +526,7 @@ RTYPEDDATA_EMBEDDED_P(VALUE obj) } #endif - return RTYPEDDATA(obj)->typed_flag & TYPED_DATA_EMBEDDED; + return (RTYPEDDATA(obj)->type) & TYPED_DATA_EMBEDDED; } static inline void * @@ -561,8 +562,7 @@ RBIMPL_ATTR_ARTIFICIAL() static inline bool rbimpl_rtypeddata_p(VALUE obj) { - VALUE typed_flag = RTYPEDDATA(obj)->typed_flag; - return typed_flag != 0 && typed_flag <= 3; + return RTYPEDDATA(obj)->type & IS_TYPED_DATA; } RBIMPL_ATTR_PURE_UNLESS_DEBUG() @@ -608,7 +608,7 @@ RTYPEDDATA_TYPE(VALUE obj) } #endif - return RTYPEDDATA(obj)->type; + return (const struct rb_data_type_struct *)(RTYPEDDATA(obj)->type & TYPED_DATA_PTR_MASK); } /** |