ruby-changes:58321
From: Koichi <ko1@a...>
Date: Sun, 20 Oct 2019 04:52:38 +0900 (JST)
Subject: [ruby-changes:58321] caac5f777a (master): make monitor.so for performance. (#2576)
https://git.ruby-lang.org/ruby.git/commit/?id=caac5f777a From caac5f777ae288b5982708b8690e712e1cae0cf6 Mon Sep 17 00:00:00 2001 From: Koichi Sasada <ko1@a...> Date: Sun, 20 Oct 2019 04:52:20 +0900 Subject: make monitor.so for performance. (#2576) Recent monitor.rb has performance problem because of interrupt handlers. 'Monitor#synchronize' is frequently used primitive so the performance of this method is important. This patch rewrite 'monitor.rb' with 'monitor.so' (C-extension) and make it faster. See [Feature #16255] for details. Monitor class objects are normal object which include MonitorMixin. This patch introduce a Monitor class which is implemented on C and MonitorMixin uses Monitor object as re-entrant (recursive) Mutex. This technique improve performance because we don't need to care atomicity and we don't need accesses to instance variables any more on Monitor class. diff --git a/NEWS b/NEWS index b72ab73..5304df4 100644 --- a/NEWS +++ b/NEWS @@ -511,6 +511,9 @@ File:: https://github.com/ruby/ruby/blob/trunk/NEWS#L511 * File.realpath now uses realpath(3) on many platforms, which can significantly improve performance. +Monitor:: + * Monitor class is written in C-extension. [Feature #16255] + Thread:: * VM stack memory allocation is now combined with native thread stack, diff --git a/ext/monitor/depend b/ext/monitor/depend new file mode 100644 index 0000000..89efe17 --- /dev/null +++ b/ext/monitor/depend @@ -0,0 +1,13 @@ https://github.com/ruby/ruby/blob/trunk/ext/monitor/depend#L1 +# AUTOGENERATED DEPENDENCIES START +monitor.o: $(RUBY_EXTCONF_H) +monitor.o: $(arch_hdrdir)/ruby/config.h +monitor.o: $(hdrdir)/ruby/assert.h +monitor.o: $(hdrdir)/ruby/backward.h +monitor.o: $(hdrdir)/ruby/defines.h +monitor.o: $(hdrdir)/ruby/intern.h +monitor.o: $(hdrdir)/ruby/missing.h +monitor.o: $(hdrdir)/ruby/ruby.h +monitor.o: $(hdrdir)/ruby/st.h +monitor.o: $(hdrdir)/ruby/subst.h +monitor.o: monitor.c +# AUTOGENERATED DEPENDENCIES END diff --git a/ext/monitor/extconf.rb b/ext/monitor/extconf.rb new file mode 100644 index 0000000..78c53fa --- /dev/null +++ b/ext/monitor/extconf.rb @@ -0,0 +1,2 @@ https://github.com/ruby/ruby/blob/trunk/ext/monitor/extconf.rb#L1 +require 'mkmf' +create_makefile('monitor') diff --git a/ext/monitor/lib/monitor.rb b/ext/monitor/lib/monitor.rb new file mode 100644 index 0000000..dba942c --- /dev/null +++ b/ext/monitor/lib/monitor.rb @@ -0,0 +1,287 @@ https://github.com/ruby/ruby/blob/trunk/ext/monitor/lib/monitor.rb#L1 +# frozen_string_literal: false +# = monitor.rb +# +# Copyright (C) 2001 Shugo Maeda <shugo@r...> +# +# This library is distributed under the terms of the Ruby license. +# You can freely distribute/modify this library. +# + +# +# In concurrent programming, a monitor is an object or module intended to be +# used safely by more than one thread. The defining characteristic of a +# monitor is that its methods are executed with mutual exclusion. That is, at +# each point in time, at most one thread may be executing any of its methods. +# This mutual exclusion greatly simplifies reasoning about the implementation +# of monitors compared to reasoning about parallel code that updates a data +# structure. +# +# You can read more about the general principles on the Wikipedia page for +# Monitors[http://en.wikipedia.org/wiki/Monitor_%28synchronization%29] +# +# == Examples +# +# === Simple object.extend +# +# require 'monitor.rb' +# +# buf = [] +# buf.extend(MonitorMixin) +# empty_cond = buf.new_cond +# +# # consumer +# Thread.start do +# loop do +# buf.synchronize do +# empty_cond.wait_while { buf.empty? } +# print buf.shift +# end +# end +# end +# +# # producer +# while line = ARGF.gets +# buf.synchronize do +# buf.push(line) +# empty_cond.signal +# end +# end +# +# The consumer thread waits for the producer thread to push a line to buf +# while <tt>buf.empty?</tt>. The producer thread (main thread) reads a +# line from ARGF and pushes it into buf then calls <tt>empty_cond.signal</tt> +# to notify the consumer thread of new data. +# +# === Simple Class include +# +# require 'monitor' +# +# class SynchronizedArray < Array +# +# include MonitorMixin +# +# def initialize(*args) +# super(*args) +# end +# +# alias :old_shift :shift +# alias :old_unshift :unshift +# +# def shift(n=1) +# self.synchronize do +# self.old_shift(n) +# end +# end +# +# def unshift(item) +# self.synchronize do +# self.old_unshift(item) +# end +# end +# +# # other methods ... +# end +# +# +SynchronizedArray+ implements an Array with synchronized access to items. +# This Class is implemented as subclass of Array which includes the +# MonitorMixin module. +# + +require 'monitor.so' + +module MonitorMixin + # + # FIXME: This isn't documented in Nutshell. + # + # Since MonitorMixin.new_cond returns a ConditionVariable, and the example + # above calls while_wait and signal, this class should be documented. + # + class ConditionVariable + # + # Releases the lock held in the associated monitor and waits; reacquires the lock on wakeup. + # + # If +timeout+ is given, this method returns after +timeout+ seconds passed, + # even if no other thread doesn't signal. + # + def wait(timeout = nil) + @monitor.mon_check_owner + count = @monitor.__send__(:exit_for_cond) + begin + @cond.wait(@monitor.__send__(:mutex_for_cond), timeout) + return true + ensure + @monitor.__send__(:enter_for_cond, count) + end + end + + # + # Calls wait repeatedly while the given block yields a truthy value. + # + def wait_while + while yield + wait + end + end + + # + # Calls wait repeatedly until the given block yields a truthy value. + # + def wait_until + until yield + wait + end + end + + # + # Wakes up the first thread in line waiting for this lock. + # + def signal + @monitor.mon_check_owner + @cond.signal + end + + # + # Wakes up all threads waiting for this lock. + # + def broadcast + @monitor.mon_check_owner + @cond.broadcast + end + + private + + def initialize(monitor) + @monitor = monitor + @cond = Thread::ConditionVariable.new + end + end + + def self.extend_object(obj) + super(obj) + obj.__send__(:mon_initialize) + end + + # + # Attempts to enter exclusive section. Returns +false+ if lock fails. + # + def mon_try_enter + @mon_data.try_enter + end + # For backward compatibility + alias try_mon_enter mon_try_enter + + # + # Enters exclusive section. + # + def mon_enter + @mon_data.enter + end + + # + # Leaves exclusive section. + # + def mon_exit + mon_check_owner + @mon_data.exit + end + + # + # Returns true if this monitor is locked by any thread + # + def mon_locked? + @mon_data.mon_locked? + end + + # + # Returns true if this monitor is locked by current thread. + # + def mon_owned? + @mon_data.mon_owned? + end + + # + # Enters exclusive section and executes the block. Leaves the exclusive + # section automatically when the block exits. See example under + # +MonitorMixin+. + # + def mon_synchronize(&b) + @mon_data.enter + begin + yield + ensure + @mon_data.exit + end + end + alias synchronize mon_synchronize + + # + # Creates a new MonitorMixin::ConditionVariable associated with the + # receiver. + # + def new_cond + return ConditionVariable.new(@mon_data) + end + + private + + # Use <tt>extend MonitorMixin</tt> or <tt>include MonitorMixin</tt> instead + # of this constructor. Have look at the examples above to understand how to + # use this module. + def initialize(*args) + super + mon_initialize + end + + # Initializes the MonitorMixin after being included in a class or when an + # object has been extended with the MonitorMixin + def mon_initialize + if defined?(@mon_data) && @mon_data_owner_object_id == self.object_id + raise ThreadError, "already initialized" + end + @mon_data = ::Monitor.new + @mon_data_owner_object_id = self.object_id + end + + def mon_check_owner + @mon_data.mon_check_owner + end +end + +# Use the Monitor class when you want to have a lock object for blocks with +# mutual exclusion. +# +# require 'monitor' +# +# lock = Monitor.new +# lock.synchronize do +# # exclusive access +# end +# +class Monitor + def new_cond + ::MonitorMixin::ConditionVariable.new(self) + end + + # for compatibility + alias try_mon_enter try_enter + alias mon_try_enter try_enter + alias mon_enter enter + alias mon_exit exit + alias mon_synchronize synchronize +end + +# Documentation comments: +# - All documentation comes from Nutshell. +# - MonitorMixin.new_cond appears in the example, but is not documented in +# Nutshell. +# - All the internals (internal modules Accessible and Initializable, class +# ConditionVariable) appear in RDoc. It might be good to hide them, by +# making them private, or marking them :nodoc:, etc. +# - RDoc doesn't recognise aliases, so we have mon_synchronize documented, but +# not synchronize. +# - mon_owner is in Nutshell, but appears as an accessor in a separate module +# here, so is hard/impossible to RDoc. Some other useful accessors +# (mon_count and some queue stuff) are also in this module, and don't appear +# directly in the RDoc output. +# - in short, it may be worth changing the code layout in this file to make the +# documentation easier diff --git a/ext/monitor/monitor.c b/ext/monitor/monitor.c new file mode 100644 index 0000000..cf9e7fe --- /dev/null +++ b/ext/monitor/monitor.c @@ -0,0 +1,189 @@ https://github.com/ruby/ruby/blob/trunk/ext/monitor/monitor.c#L1 +#include "ruby/ruby.h" + +/* Thread::Monitor */ + +struct rb_monitor { + long count; + const VA (... truncated) -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/