Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 49 additions & 7 deletions array.c
Original file line number Diff line number Diff line change
Expand Up @@ -6837,6 +6837,22 @@ flatten(VALUE ary, int level)
return result;
}

static inline VALUE
single_nested_array(VALUE ary)
{
// Fast path for the common variadic argument pattern:
// def foo(*args)
// args.flatten!
// ...
if (RARRAY_LEN(ary) == 1) {
VALUE first = RARRAY_AREF(ary, 0);
if (RB_TYPE_P(first, T_ARRAY) && CLASS_OF(first) == rb_cArray) {
return first;
}
}
return 0;
}

/*
* call-seq:
* flatten!(depth = nil) -> self or nil
Expand Down Expand Up @@ -6883,11 +6899,24 @@ rb_ary_flatten_bang(int argc, VALUE *argv, VALUE ary)
if (!NIL_P(lv)) level = NUM2INT(lv);
if (level == 0) return Qnil;

result = flatten(ary, level);
if (result == ary) {
return Qnil;
VALUE child = single_nested_array(ary);
if (child) {
if (level == 1) {
result = child;
}
else {
if (level > 1) level--;
result = flatten(child, level);
}
}
else {
result = flatten(ary, level);
if (result == ary) {
return Qnil;
}
}
if (!(mod = ARY_EMBED_P(result))) rb_ary_freeze(result);

if (!(mod = ARY_EMBED_P(result) && result != child)) rb_ary_freeze(result);
rb_ary_replace(ary, result);
if (mod) ARY_SET_EMBED_LEN(result, 0);

Expand Down Expand Up @@ -6940,9 +6969,22 @@ rb_ary_flatten(int argc, VALUE *argv, VALUE ary)
if (level == 0) return ary_make_shared_copy(ary);
}

result = flatten(ary, level);
if (result == ary) {
result = ary_make_shared_copy(ary);
VALUE child = single_nested_array(ary);
if (child) {
if (level == 1) {
result = child;
}
else {
level--;
result = flatten(child, level);
}
}
else {
result = flatten(ary, level);
}

if (result == ary || result == child) {
return ary_make_shared_copy(result);
}

return result;
Expand Down
14 changes: 12 additions & 2 deletions benchmark/array_flatten.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,26 @@ prelude: |
small_pairs_ary = [[1, 2]] * 5
large_pairs_ary = [[1, 2]] * 100
mostly_flat_ary = 100.times.to_a.push([101, 102])
small_nested_ary = [small_flat_ary]
large_nested_ary = [large_flat_ary]

benchmark:
small_flat_ary.flatten: small_flat_ary.flatten
small_flat_ary.flatten!: small_flat_ary.flatten!
small_flat_ary.flatten!: small_flat_ary.dup.flatten!
large_flat_ary.flatten: large_flat_ary.flatten
large_flat_ary.flatten!: large_flat_ary.flatten!
large_flat_ary.flatten!: large_flat_ary.dup.flatten!
small_pairs_ary.flatten: small_pairs_ary.flatten
small_pairs_ary.flatten!: small_pairs_ary.dup.flatten!
large_pairs_ary.flatten: large_pairs_ary.flatten
large_pairs_ary.flatten!: large_pairs_ary.dup.flatten!
mostly_flat_ary.flatten: mostly_flat_ary.flatten
mostly_flat_ary.flatten!: mostly_flat_ary.dup.flatten!
small_nested_ary.flatten: small_nested_ary.flatten
small_nested_ary.flatten!: small_nested_ary.dup.flatten!
large_nested_ary.flatten: large_nested_ary.flatten
large_nested_ary.flatten!: large_nested_ary.dup.flatten!
small_nested_ary.flatten(1): small_nested_ary.flatten(1)
small_nested_ary.flatten!(1): small_nested_ary.dup.flatten!(1)
large_nested_ary.flatten(1): large_nested_ary.flatten(1)
large_nested_ary.flatten!(1): large_nested_ary.dup.flatten!(1)
loop_count: 10000
7 changes: 5 additions & 2 deletions gc.c
Original file line number Diff line number Diff line change
Expand Up @@ -4183,9 +4183,12 @@ rb_gc_obj_foreign_p(VALUE obj)
bool
rb_gc_single_objspace_p(void)
{
if (!rb_gc_impl_multi_objspace_p() || ruby_single_main_ractor) return true;
if (!rb_gc_impl_multi_objspace_p()) return true;
rb_vm_t *vm = GET_VM();
return vm->ractor.cnt == 1 && vm->gc.zombie_objspaces_count == 0 && gc_absorbing_zombie == 0 &&
/* One Ractor is not one objspace: a forked child re-enters single-Ractor mode while
* the pre-fork Ractors' objspaces are still parked in zombie_objspaces. */
return (ruby_single_main_ractor != NULL || vm->ractor.cnt == 1) &&
vm->gc.zombie_objspaces_count == 0 && gc_absorbing_zombie == 0 &&
!gc_absorbed_since_global_gc &&
(vm->ractor.main_ractor == NULL ||
vm->ractor.main_ractor->creating_child_objspace == NULL);
Expand Down
10 changes: 3 additions & 7 deletions io_buffer.c
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ io_buffer_zero(struct rb_io_buffer *buffer)
{
buffer->base = NULL;
buffer->size = 0;
buffer->flags = 0;
buffer->lock_count = 0;
#if defined(_WIN32)
buffer->mapping = NULL;
Expand Down Expand Up @@ -259,13 +260,6 @@ io_buffer_free(struct rb_io_buffer *buffer)
// if (RB_TYPE_P(buffer->source, T_STRING)) {
// rb_str_unlocktmp(buffer->source);
// }

buffer->base = NULL;

buffer->size = 0;
buffer->flags = 0;
buffer->lock_count = 0;
buffer->source = Qnil;
}

#if defined(_WIN32)
Expand All @@ -277,6 +271,8 @@ io_buffer_free(struct rb_io_buffer *buffer)
buffer->mapping = NULL;
}
#endif

io_buffer_zero(buffer);
}

static void
Expand Down
10 changes: 10 additions & 0 deletions test/ruby/test_io_buffer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,16 @@ def test_transfer
transferred = buffer.transfer
assert_equal "Hello World", transferred.get_string
assert_predicate buffer, :null?
assert_predicate buffer, :empty?
assert_predicate buffer, :valid?
refute_predicate buffer, :external?
refute_predicate buffer, :internal?
refute_predicate buffer, :mapped?
refute_predicate buffer, :shared?
refute_predicate buffer, :private?
refute_predicate buffer, :readonly?
assert_equal "", buffer.get_string
assert_equal 0, buffer.set_string("")
assert_raise IO::Buffer::AccessError do
transferred.set_string("Goodbye")
end
Expand Down
15 changes: 15 additions & 0 deletions test/ruby/test_ractor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,21 @@ def test_create_many_ports_with_gc_stress
RUBY
end

def test_fork_child_gc_pins_shareable_objects
# A forked child re-enters single-Ractor mode while the Ractors it had before the
# fork leave their objspaces behind, so its local GC still has to pin shareable
# objects instead of collecting them.
assert_ractor(<<~'RUBY')
port = Ractor::Port.new
Ractor.new(port) { |p| p << Ractor::Port.new; Ractor.receive }
foreign_port = port.receive # a Port owned by, and allocated in, the other Ractor
pid = fork { 100_000.times { +"x" }; exit!(0) }
_, status = Process.waitpid2(pid)
assert_predicate status, :success?
assert_instance_of Ractor::Port, foreign_port
RUBY
end if Process.respond_to?(:fork)

def test_fork_raise_isolation_error
assert_ractor(<<~'RUBY')
ractor = Ractor.new do
Expand Down
23 changes: 6 additions & 17 deletions thread.c
Original file line number Diff line number Diff line change
Expand Up @@ -836,6 +836,9 @@ thread_start_func_2(rb_thread_t *th, VALUE *stack_start)

#if defined(USE_MN_THREADS) && USE_MN_THREADS
if (th_has_coroutine(th)) {
// wait out any pending wake while th and its Ractor are still alive
rb_thread_wake_fence(th);

// Run the coroutine thread's epilogue here, while th is still valid;
// co_start then only makes the final transfer (see
// coroutine_thread_terminated in thread_pthread_mn.c).
Expand Down Expand Up @@ -1506,26 +1509,12 @@ hrtime_update_expire(rb_hrtime_t *timeout, const rb_hrtime_t end)
}
COMPILER_WARNING_POP

static int sleep_hrtime_until(rb_thread_t *th, rb_hrtime_t end, unsigned int fl);

static int
sleep_hrtime(rb_thread_t *th, rb_hrtime_t rel, unsigned int fl)
{
enum rb_thread_status prev_status = th->status;
int woke;
rb_hrtime_t end = rb_hrtime_add(rb_hrtime_now(), rel);

th->status = THREAD_STOPPED;
RUBY_VM_CHECK_INTS_BLOCKING(th->ec);
while (th->status == THREAD_STOPPED) {
native_sleep(th, &rel);
woke = vm_check_ints_blocking(th->ec);
if (woke && !(fl & SLEEP_SPURIOUS_CHECK))
break;
if (hrtime_update_expire(&rel, end))
break;
woke = 1;
}
th->status = prev_status;
return woke;
return sleep_hrtime_until(th, rb_hrtime_add(rb_hrtime_now(), rel), fl);
}

static int
Expand Down
Loading