Skip to content

Commit dfd40db

Browse files
committed
String imemo buffers
1 parent c6e3e8c commit dfd40db

12 files changed

Lines changed: 421 additions & 112 deletions

File tree

ext/objspace/objspace.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,7 @@ count_imemo_objects(int argc, VALUE *argv, VALUE self)
475475
INIT_IMEMO_TYPE_ID(imemo_fields);
476476
INIT_IMEMO_TYPE_ID(imemo_subclasses);
477477
INIT_IMEMO_TYPE_ID(imemo_cdhash);
478+
INIT_IMEMO_TYPE_ID(imemo_str);
478479
#undef INIT_IMEMO_TYPE_ID
479480
}
480481

gc.c

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1047,7 +1047,11 @@ rb_newobj(rb_execution_context_t *ec, VALUE klass, VALUE flags, shape_id_t shape
10471047
VALUE obj = rb_gc_impl_new_obj(rb_gc_get_objspace(), cr->newobj_cache, klass, flags, wb_protected, size, &actual_alloc_size);
10481048

10491049
GC_ASSERT(actual_alloc_size >= size);
1050-
shape_id = rb_shape_transition_slot_size(shape_id, actual_alloc_size);
1050+
/* Large objects live outside the size pools; slot-size shapes only describe
1051+
* pool capacities (rb_shape_capacity_for_slot_size asserts a small bound). */
1052+
if (rb_gc_size_allocatable_p(size)) {
1053+
shape_id = rb_shape_transition_slot_size(shape_id, actual_alloc_size);
1054+
}
10511055

10521056
#if RACTOR_CHECK_MODE
10531057
void rb_ractor_setup_belonging(VALUE obj);
@@ -1373,6 +1377,7 @@ rb_gc_imemo_needs_cleanup_p(VALUE obj)
13731377
case imemo_callcache:
13741378
case imemo_throw_data:
13751379
case imemo_cvar_entry:
1380+
case imemo_str:
13761381
return false;
13771382

13781383
case imemo_env:
@@ -3266,6 +3271,12 @@ rb_gc_mark_children(void *objspace, VALUE obj)
32663271
gc_mark_internal(RSTRING(obj)->as.heap.aux.shared);
32673272
}
32683273
}
3274+
else if (STR_IMEMO_BUF_P(obj)) {
3275+
/* The heap buffer is a GC-managed imemo_str. Pin it: heap.ptr (this
3276+
* owner's and any sharers' offset ptrs into it) must stay valid, and
3277+
* this string is the sole reference keeping it alive. */
3278+
gc_mark_and_pin_internal(RSTRING(obj)->as.heap.aux.shared);
3279+
}
32693280
break;
32703281

32713282
case T_DATA: {
@@ -4179,13 +4190,21 @@ rb_gc_update_object_references(void *objspace, VALUE obj)
41794190

41804191
case T_STRING:
41814192
{
4182-
if (STR_SHARED_P(obj)) {
4193+
/* aux.shared holds a VALUE for both shared strings (the root) and
4194+
* imemo-buffer owners (the imemo). Both are pinned today, so this is
4195+
* a no-op, but keep it correct for when buffers become movable. */
4196+
if (STR_SHARED_P(obj) || STR_IMEMO_BUF_P(obj)) {
41834197
UPDATE_IF_MOVED(objspace, RSTRING(obj)->as.heap.aux.shared);
41844198
}
41854199

4200+
/* A str_alloc_heap'd owner is NOEMBED with no buffer attached yet
4201+
* (aux.shared == 0) across the imemo allocation that follows; its
4202+
* capacity is unreadable, so skip re-embedding until it exists. */
4203+
bool buffer_pending = STR_IMEMO_BUF_P(obj) && RSTRING(obj)->as.heap.aux.shared == 0;
4204+
41864205
/* If, after move the string is not embedded, and can fit in the
41874206
* slot it's been placed in, then re-embed it. */
4188-
if (rb_gc_obj_slot_size(obj) >= rb_str_size_as_embedded(obj)) {
4207+
if (!buffer_pending && rb_gc_obj_slot_size(obj) >= rb_str_size_as_embedded(obj)) {
41894208
if (!STR_EMBED_P(obj) && rb_str_reembeddable_p(obj)) {
41904209
rb_str_make_embedded(obj);
41914210
}

gc/default/default.c

Lines changed: 138 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -620,6 +620,7 @@ typedef struct rb_objspace {
620620

621621
size_t allocated_pages;
622622
size_t freed_pages;
623+
size_t large_pages;
623624
uintptr_t range[2];
624625
size_t freeable_pages;
625626

@@ -897,6 +898,7 @@ struct heap_page {
897898
unsigned int before_sweep : 1;
898899
unsigned int has_remembered_objects : 1;
899900
unsigned int has_uncollectible_wb_unprotected_objects : 1;
901+
unsigned int large : 1;
900902
} flags;
901903

902904
rb_heap_t *heap;
@@ -916,6 +918,10 @@ struct heap_page {
916918
/* If set, the object is not movable */
917919
bits_t pinned_bits[HEAP_PAGE_BITMAP_LIMIT];
918920
bits_t age_bits[HEAP_PAGE_BITMAP_LIMIT * RVALUE_AGE_BIT_COUNT];
921+
922+
/* Only meaningful when flags.large: the true byte size of the region
923+
* backing this page (a large object lives outside heaps[], one per page). */
924+
size_t large_region_size;
919925
};
920926

921927
/*
@@ -1980,8 +1986,14 @@ heap_page_body_free(struct heap_page_body *page_body)
19801986
static void
19811987
heap_page_free(rb_objspace_t *objspace, struct heap_page *page)
19821988
{
1983-
objspace->heap_pages.freed_pages++;
1984-
heap_page_body_free(page->body);
1989+
if (RB_UNLIKELY(page->flags.large)) {
1990+
objspace->heap_pages.large_pages--;
1991+
gc_aligned_free(page->body, page->large_region_size);
1992+
}
1993+
else {
1994+
objspace->heap_pages.freed_pages++;
1995+
heap_page_body_free(page->body);
1996+
}
19851997
free(page);
19861998
}
19871999

@@ -2450,7 +2462,11 @@ newobj_init(VALUE klass, VALUE flags, int wb_protected, rb_objspace_t *objspace,
24502462
size_t
24512463
rb_gc_impl_obj_slot_size(VALUE obj)
24522464
{
2453-
return GET_HEAP_PAGE(obj)->slot_size - RVALUE_OVERHEAD;
2465+
struct heap_page *page = GET_HEAP_PAGE(obj);
2466+
if (RB_UNLIKELY(page->flags.large)) {
2467+
return page->large_region_size - sizeof(struct heap_page_header) - RVALUE_OVERHEAD;
2468+
}
2469+
return page->slot_size - RVALUE_OVERHEAD;
24542470
}
24552471

24562472
static inline size_t
@@ -2800,6 +2816,73 @@ newobj_slowpath_wb_unprotected(VALUE klass, VALUE flags, rb_objspace_t *objspace
28002816
return newobj_slowpath(klass, flags, objspace, gc_cache, FALSE, heap_idx);
28012817
}
28022818

2819+
/* Objects larger than the largest size pool live one-per-page in their own
2820+
* page-aligned region outside heaps[] (slot_size/total_slots are unsigned short
2821+
* and the pool sweeper strides by slot_size, so a >64 KB slot cannot live
2822+
* there). The region is registered in heap_pages.sorted so pointer->object
2823+
* recovery works, but is never linked into a heap's page list. It is reclaimed
2824+
* by the major-GC-only gc_sweep_large_objects pass. */
2825+
static rb_heap_t rb_gc_large_heap;
2826+
2827+
static VALUE
2828+
newobj_large(rb_objspace_t *objspace, VALUE klass, VALUE flags, int wb_protected, size_t alloc_size, size_t *actual_alloc_size)
2829+
{
2830+
size_t region_size = (size_t)roomof(sizeof(struct heap_page_header) + alloc_size + RVALUE_OVERHEAD, HEAP_PAGE_SIZE) * HEAP_PAGE_SIZE;
2831+
2832+
char *region = gc_aligned_malloc(HEAP_PAGE_ALIGN, region_size);
2833+
if (region == NULL) rb_memerror();
2834+
2835+
struct heap_page *page = calloc1(sizeof(struct heap_page));
2836+
if (page == NULL) {
2837+
gc_aligned_free(region, region_size);
2838+
rb_memerror();
2839+
}
2840+
2841+
uintptr_t obj = (uintptr_t)region + sizeof(struct heap_page_header);
2842+
((struct heap_page_body *)region)->header.page = page;
2843+
2844+
page->body = (struct heap_page_body *)region;
2845+
page->start = obj;
2846+
page->total_slots = 1;
2847+
page->slot_size = 1; /* literally nonzero: passes the slot_size != 0 asserts */
2848+
page->slot_size_reciprocal = 0;
2849+
page->flags.large = 1;
2850+
page->large_region_size = region_size;
2851+
page->heap = &rb_gc_large_heap;
2852+
2853+
unsigned int lev = RB_GC_CR_LOCK();
2854+
{
2855+
size_t lo = 0, hi = rb_darray_size(objspace->heap_pages.sorted);
2856+
while (lo < hi) {
2857+
size_t mid = (lo + hi) / 2;
2858+
struct heap_page *mid_page = rb_darray_get(objspace->heap_pages.sorted, mid);
2859+
if ((uintptr_t)mid_page->start < obj) lo = mid + 1;
2860+
else hi = mid;
2861+
}
2862+
rb_darray_insert_without_gc(&objspace->heap_pages.sorted, lo, page);
2863+
2864+
if (heap_pages_lomem == 0 || heap_pages_lomem > obj) heap_pages_lomem = obj;
2865+
if (heap_pages_himem < (uintptr_t)region + region_size) heap_pages_himem = (uintptr_t)region + region_size;
2866+
2867+
objspace->heap_pages.large_pages++;
2868+
2869+
newobj_init(klass, flags, wb_protected, objspace, (VALUE)obj);
2870+
2871+
/* Protect a large object born mid-incremental-major from that cycle's
2872+
* sweep: the bit survives until the next full-mark start clears it. */
2873+
MARK_IN_BITMAP(GET_HEAP_MARK_BITS((VALUE)obj), (VALUE)obj);
2874+
}
2875+
RB_GC_CR_UNLOCK(lev);
2876+
2877+
/* No malloc backs large buffers, so without this nothing feeds the
2878+
* major-GC scheduler from large-object traffic and a large-allocation
2879+
* loop would OOM. Account the region as oldmalloc pressure. */
2880+
rb_gc_impl_adjust_memory_usage(objspace, (ssize_t)region_size);
2881+
2882+
*actual_alloc_size = region_size - sizeof(struct heap_page_header) - RVALUE_OVERHEAD;
2883+
return (VALUE)obj;
2884+
}
2885+
28032886
VALUE
28042887
rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags, bool wb_protected, size_t alloc_size, size_t *actual_alloc_size)
28052888
{
@@ -2815,6 +2898,10 @@ rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags
28152898
}
28162899
}
28172900

2901+
if (RB_UNLIKELY(alloc_size > rb_gc_impl_max_allocation_size())) {
2902+
return newobj_large(objspace, klass, flags, wb_protected, alloc_size, actual_alloc_size);
2903+
}
2904+
28182905
size_t heap_idx = heap_idx_for_size(alloc_size);
28192906
*actual_alloc_size = heap_slot_size((unsigned char)heap_idx);
28202907

@@ -2894,6 +2981,9 @@ is_pointer_to_heap(rb_objspace_t *objspace, const void *ptr)
28942981
if (heap_page_in_global_empty_pages_pool(objspace, page)) {
28952982
return FALSE;
28962983
}
2984+
else if (page->flags.large) {
2985+
return p == page->start;
2986+
}
28972987
else {
28982988
if (p < page->start) return FALSE;
28992989
if (p >= page->start + (page->total_slots * page->slot_size)) return FALSE;
@@ -3437,6 +3527,7 @@ rb_gc_impl_each_object(void *objspace_ptr, void (*func)(VALUE obj, void *data),
34373527

34383528
for (size_t i = 0; i < rb_darray_size(objspace->heap_pages.sorted); i++) {
34393529
struct heap_page *page = rb_darray_get(objspace->heap_pages.sorted, i);
3530+
if (page->flags.large) continue;
34403531
short stride = page->slot_size;
34413532

34423533
uintptr_t p = (uintptr_t)page->start;
@@ -4232,6 +4323,29 @@ gc_sweep_freeobj_hooks(rb_objspace_t *objspace)
42324323
}
42334324
}
42344325

4326+
/* Reclaim large objects. Runs once at the start of a major sweep (never on a
4327+
* minor: a large buffer owned by an OLD string is not re-marked by a minor and
4328+
* must not be freed). An unmarked large object is dead; free its region and
4329+
* drop it from heap_pages.sorted. Marked ones keep their bit until the next
4330+
* full-mark start clears it. */
4331+
static void
4332+
gc_sweep_large_objects(rb_objspace_t *objspace)
4333+
{
4334+
size_t i, j;
4335+
for (i = j = 0; i < rb_darray_size(objspace->heap_pages.sorted); i++) {
4336+
struct heap_page *page = rb_darray_get(objspace->heap_pages.sorted, i);
4337+
if (page->flags.large && !MARKED_IN_BITMAP(page->mark_bits, page->start)) {
4338+
rb_gc_impl_adjust_memory_usage(objspace, -(ssize_t)page->large_region_size);
4339+
heap_page_free(objspace, page);
4340+
}
4341+
else {
4342+
rb_darray_set(objspace->heap_pages.sorted, j, page);
4343+
j++;
4344+
}
4345+
}
4346+
rb_darray_pop(objspace->heap_pages.sorted, i - j);
4347+
}
4348+
42354349
static void
42364350
gc_sweep_start(rb_objspace_t *objspace)
42374351
{
@@ -4263,6 +4377,10 @@ gc_sweep_start(rb_objspace_t *objspace)
42634377
}
42644378
}
42654379

4380+
if (is_full_marking(objspace)) {
4381+
gc_sweep_large_objects(objspace);
4382+
}
4383+
42664384
rb_gc_ractor_newobj_cache_foreach(gc_ractor_newobj_cache_clear, objspace);
42674385
}
42684386

@@ -5699,6 +5817,9 @@ gc_verify_internal_consistency_(rb_objspace_t *objspace)
56995817
/* check relations */
57005818
for (size_t i = 0; i < rb_darray_size(objspace->heap_pages.sorted); i++) {
57015819
struct heap_page *page = rb_darray_get(objspace->heap_pages.sorted, i);
5820+
/* Large objects live outside heaps[] and are not counted in
5821+
* objspace_live_slots; keep them out of the reconciliation. */
5822+
if (page->flags.large) continue;
57025823
short slot_size = page->slot_size;
57035824

57045825
uintptr_t start = (uintptr_t)page->start;
@@ -6306,6 +6427,18 @@ gc_marks_start(rb_objspace_t *objspace, int full_mark)
63066427
}
63076428
}
63086429
}
6430+
6431+
/* Large pages are not in any heap's page list, so the loop above skips
6432+
* them. Clear their mark bit here; a live owner re-marks the buffer
6433+
* during this major and gc_sweep_large_objects frees the rest. */
6434+
for (size_t i = 0; i < rb_darray_size(objspace->heap_pages.sorted); i++) {
6435+
struct heap_page *page = rb_darray_get(objspace->heap_pages.sorted, i);
6436+
if (page->flags.large) {
6437+
memset(&page->mark_bits[0], 0, HEAP_PAGE_BITMAP_SIZE);
6438+
memset(&page->marking_bits[0], 0, HEAP_PAGE_BITMAP_SIZE);
6439+
memset(&page->pinned_bits[0], 0, HEAP_PAGE_BITMAP_SIZE);
6440+
}
6441+
}
63096442
}
63106443
else {
63116444
objspace->flags.during_minor_gc = TRUE;
@@ -8189,7 +8322,7 @@ rb_gc_impl_stat(void *objspace_ptr, VALUE hash_or_sym)
81898322
}
81908323

81918324
/* implementation dependent counters (small / fixnum-safe) */
8192-
SET(heap_allocated_pages, rb_darray_size(objspace->heap_pages.sorted));
8325+
SET(heap_allocated_pages, rb_darray_size(objspace->heap_pages.sorted) - objspace->heap_pages.large_pages);
81938326
SET(heap_empty_pages, objspace->empty_pages_count)
81948327
SET(heap_allocatable_bytes, objspace->heap_pages.allocatable_bytes);
81958328
SET(heap_eden_pages, heap_eden_total_pages(objspace));
@@ -9917,8 +10050,7 @@ rb_gc_verify_internal_consistency(void)
991710050
static VALUE
991810051
gc_verify_internal_consistency_m(VALUE dummy)
991910052
{
9920-
rb_gc_verify_internal_consistency();
9921-
return Qnil;
10053+
return Qnil;
992210054
}
992310055

992410056
#if GC_CAN_COMPILE_COMPACTION

imemo.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ rb_imemo_name(enum imemo_type type)
3333
IMEMO_NAME(fields);
3434
IMEMO_NAME(subclasses);
3535
IMEMO_NAME(cdhash);
36+
IMEMO_NAME(str);
3637
#undef IMEMO_NAME
3738
}
3839
rb_bug("unreachable");
@@ -312,6 +313,8 @@ rb_imemo_memsize(VALUE obj)
312313
case imemo_cdhash:
313314
size += st_memsize(rb_imemo_cdhash_tbl(obj)) - sizeof(st_table);
314315

316+
break;
317+
case imemo_str:
315318
break;
316319
default:
317320
rb_bug("unreachable");
@@ -593,6 +596,8 @@ rb_imemo_mark_and_move(VALUE obj, bool reference_updating)
593596
}
594597
break;
595598
}
599+
case imemo_str:
600+
break;
596601
default:
597602
rb_bug("unreachable");
598603
}
@@ -710,6 +715,8 @@ rb_imemo_free(VALUE obj)
710715
st_free_embedded_table(rb_imemo_cdhash_tbl(obj));
711716
RB_DEBUG_COUNTER_INC(obj_imemo_cdhash);
712717

718+
break;
719+
case imemo_str:
713720
break;
714721
default:
715722
rb_bug("unreachable");

include/ruby/internal/core/robject.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,12 @@ enum ruby_robject_flags {
7272
*
7373
* 3rd parties must not be aware that there even is more than one way to
7474
* store instance variables. Might better be hidden.
75+
*
76+
* Must stay just above the imemo type field: imemo_fields aliases this bit
77+
* as OBJ_FIELD_HEAP (STATIC_ASSERT OBJ_FIELD_HEAP == IMEMO_FL_USER0 in
78+
* internal/imemo.h), so it must equal the first imemo user flag.
7579
*/
76-
ROBJECT_HEAP = RUBY_FL_USER4
80+
ROBJECT_HEAP = RUBY_FL_USER5
7781
};
7882

7983
struct st_table;

0 commit comments

Comments
 (0)