summaryrefslogtreecommitdiff
path: root/include/ruby/internal
diff options
context:
space:
mode:
authorJeremy Evans <[email protected]>2025-04-27 17:23:04 -0700
committerJeremy Evans <[email protected]>2025-05-05 09:46:32 +0900
commitce51ef30df5bf07ec3881a377f0011b8f20ec507 (patch)
tree27878fa2e8e428bcb43ca685f6904627fedc6080 /include/ruby/internal
parent35918df740018a510d0f9434e6eca2a2ad533003 (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/internal')
-rw-r--r--include/ruby/internal/core/rdata.h9
-rw-r--r--include/ruby/internal/core/rtypeddata.h26
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);
}
/**