Skip to content

Commit ce51ef3

Browse files
committed
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.
1 parent 35918df commit ce51ef3

File tree

3 files changed

+28
-25
lines changed

3 files changed

+28
-25
lines changed

gc.c

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1064,7 +1064,7 @@ rb_data_object_wrap(VALUE klass, void *datap, RUBY_DATA_FUNC dmark, RUBY_DATA_FU
10641064
{
10651065
RUBY_ASSERT_ALWAYS(dfree != (RUBY_DATA_FUNC)1);
10661066
if (klass) rb_data_object_check(klass);
1067-
return newobj_of(GET_RACTOR(), klass, T_DATA, (VALUE)dmark, (VALUE)dfree, (VALUE)datap, !dmark, sizeof(struct RTypedData));
1067+
return newobj_of(GET_RACTOR(), klass, T_DATA, (VALUE)dmark, (VALUE)datap, (VALUE)dfree, !dmark, sizeof(struct RTypedData));
10681068
}
10691069

10701070
VALUE
@@ -1081,7 +1081,7 @@ typed_data_alloc(VALUE klass, VALUE typed_flag, void *datap, const rb_data_type_
10811081
RBIMPL_NONNULL_ARG(type);
10821082
if (klass) rb_data_object_check(klass);
10831083
bool wb_protected = (type->flags & RUBY_FL_WB_PROTECTED) || !type->function.dmark;
1084-
return newobj_of(GET_RACTOR(), klass, T_DATA, (VALUE)type, 1 | typed_flag, (VALUE)datap, wb_protected, size);
1084+
return newobj_of(GET_RACTOR(), klass, T_DATA, ((VALUE)type) | IS_TYPED_DATA | typed_flag, (VALUE)datap, 0, wb_protected, size);
10851085
}
10861086

10871087
VALUE
@@ -1177,8 +1177,8 @@ rb_data_free(void *objspace, VALUE obj)
11771177
void (*dfree)(void *);
11781178

11791179
if (RTYPEDDATA_P(obj)) {
1180-
free_immediately = (RTYPEDDATA(obj)->type->flags & RUBY_TYPED_FREE_IMMEDIATELY) != 0;
1181-
dfree = RTYPEDDATA(obj)->type->function.dfree;
1180+
free_immediately = (RTYPEDDATA_TYPE(obj)->flags & RUBY_TYPED_FREE_IMMEDIATELY) != 0;
1181+
dfree = RTYPEDDATA_TYPE(obj)->function.dfree;
11821182
}
11831183
else {
11841184
dfree = RDATA(obj)->dfree;
@@ -2660,7 +2660,7 @@ rb_gc_mark_roots(void *objspace, const char **categoryp)
26602660
#undef MARK_CHECKPOINT
26612661
}
26622662

2663-
#define TYPED_DATA_REFS_OFFSET_LIST(d) (size_t *)(uintptr_t)RTYPEDDATA(d)->type->function.dmark
2663+
#define TYPED_DATA_REFS_OFFSET_LIST(d) (size_t *)(uintptr_t)RTYPEDDATA_TYPE(d)->function.dmark
26642664

26652665
void
26662666
rb_gc_mark_children(void *objspace, VALUE obj)
@@ -2780,7 +2780,7 @@ rb_gc_mark_children(void *objspace, VALUE obj)
27802780
void *const ptr = RTYPEDDATA_P(obj) ? RTYPEDDATA_GET_DATA(obj) : DATA_PTR(obj);
27812781

27822782
if (ptr) {
2783-
if (RTYPEDDATA_P(obj) && gc_declarative_marking_p(RTYPEDDATA(obj)->type)) {
2783+
if (RTYPEDDATA_P(obj) && gc_declarative_marking_p(RTYPEDDATA_TYPE(obj))) {
27842784
size_t *offset_list = TYPED_DATA_REFS_OFFSET_LIST(obj);
27852785

27862786
for (size_t offset = *offset_list; offset != RUBY_REF_END; offset = *offset_list++) {
@@ -2789,7 +2789,7 @@ rb_gc_mark_children(void *objspace, VALUE obj)
27892789
}
27902790
else {
27912791
RUBY_DATA_FUNC mark_func = RTYPEDDATA_P(obj) ?
2792-
RTYPEDDATA(obj)->type->function.dmark :
2792+
RTYPEDDATA_TYPE(obj)->function.dmark :
27932793
RDATA(obj)->dmark;
27942794
if (mark_func) (*mark_func)(ptr);
27952795
}
@@ -3717,7 +3717,7 @@ rb_gc_update_object_references(void *objspace, VALUE obj)
37173717
{
37183718
void *const ptr = RTYPEDDATA_P(obj) ? RTYPEDDATA_GET_DATA(obj) : DATA_PTR(obj);
37193719
if (ptr) {
3720-
if (RTYPEDDATA_P(obj) && gc_declarative_marking_p(RTYPEDDATA(obj)->type)) {
3720+
if (RTYPEDDATA_P(obj) && gc_declarative_marking_p(RTYPEDDATA_TYPE(obj))) {
37213721
size_t *offset_list = TYPED_DATA_REFS_OFFSET_LIST(obj);
37223722

37233723
for (size_t offset = *offset_list; offset != RUBY_REF_END; offset = *offset_list++) {
@@ -3726,7 +3726,7 @@ rb_gc_update_object_references(void *objspace, VALUE obj)
37263726
}
37273727
}
37283728
else if (RTYPEDDATA_P(obj)) {
3729-
RUBY_DATA_FUNC compact_func = RTYPEDDATA(obj)->type->function.dcompact;
3729+
RUBY_DATA_FUNC compact_func = RTYPEDDATA_TYPE(obj)->function.dcompact;
37303730
if (compact_func) (*compact_func)(ptr);
37313731
}
37323732
}

include/ruby/internal/core/rdata.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,12 @@ struct RData {
133133
*/
134134
RUBY_DATA_FUNC dmark;
135135

136+
/** Pointer to the actual C level struct that you want to wrap.
137+
* This is in between dmark and dfree to allow DATA_PTR to continue
138+
* to work for both RData and non-embedded RTypedData.
139+
*/
140+
void *data;
141+
136142
/**
137143
* This function is called when the object is no longer used. You need to
138144
* do whatever necessary to avoid memory leaks.
@@ -141,9 +147,6 @@ struct RData {
141147
* impossible at that moment (that is why GC runs).
142148
*/
143149
RUBY_DATA_FUNC dfree;
144-
145-
/** Pointer to the actual C level struct that you want to wrap. */
146-
void *data;
147150
};
148151

149152
RBIMPL_SYMBOL_EXPORT_BEGIN()

include/ruby/internal/core/rtypeddata.h

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,10 @@
114114
#define RUBY_TYPED_PROMOTED1 RUBY_TYPED_PROMOTED1
115115
/** @endcond */
116116

117-
#define TYPED_DATA_EMBEDDED 2
117+
#define IS_TYPED_DATA ((VALUE)1)
118+
#define TYPED_DATA_EMBEDDED ((VALUE)2)
119+
#define TYPED_DATA_PTR_FLAGS ((VALUE)3)
120+
#define TYPED_DATA_PTR_MASK (~TYPED_DATA_PTR_FLAGS)
118121

119122
/**
120123
* @private
@@ -353,18 +356,16 @@ struct RTypedData {
353356
struct RBasic basic;
354357

355358
/**
359+
* This is a `const rb_data_type_t *const` value, with the low bits set:
360+
*
361+
* 1: Always set, to differentiate RTypedData from RData.
362+
* 2: Set if object is embedded.
363+
*
356364
* This field stores various information about how Ruby should handle a
357365
* data. This roughly resembles a Ruby level class (apart from method
358366
* definition etc.)
359367
*/
360-
const rb_data_type_t *const type;
361-
362-
/**
363-
* This has to be always 1.
364-
*
365-
* @internal
366-
*/
367-
const VALUE typed_flag;
368+
const VALUE type;
368369

369370
/** Pointer to the actual C level struct that you want to wrap. */
370371
void *data;
@@ -525,7 +526,7 @@ RTYPEDDATA_EMBEDDED_P(VALUE obj)
525526
}
526527
#endif
527528

528-
return RTYPEDDATA(obj)->typed_flag & TYPED_DATA_EMBEDDED;
529+
return (RTYPEDDATA(obj)->type) & TYPED_DATA_EMBEDDED;
529530
}
530531

531532
static inline void *
@@ -561,8 +562,7 @@ RBIMPL_ATTR_ARTIFICIAL()
561562
static inline bool
562563
rbimpl_rtypeddata_p(VALUE obj)
563564
{
564-
VALUE typed_flag = RTYPEDDATA(obj)->typed_flag;
565-
return typed_flag != 0 && typed_flag <= 3;
565+
return RTYPEDDATA(obj)->type & IS_TYPED_DATA;
566566
}
567567

568568
RBIMPL_ATTR_PURE_UNLESS_DEBUG()
@@ -608,7 +608,7 @@ RTYPEDDATA_TYPE(VALUE obj)
608608
}
609609
#endif
610610

611-
return RTYPEDDATA(obj)->type;
611+
return (const struct rb_data_type_struct *)(RTYPEDDATA(obj)->type & TYPED_DATA_PTR_MASK);
612612
}
613613

614614
/**

0 commit comments

Comments
 (0)