concurrently 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -1
- data/.travis.yml +8 -3
- data/README.md +70 -60
- data/RELEASE_NOTES.md +16 -1
- data/Rakefile +98 -14
- data/concurrently.gemspec +16 -12
- data/ext/mruby/io.rb +1 -1
- data/guides/Overview.md +191 -66
- data/guides/Performance.md +300 -102
- data/guides/Troubleshooting.md +28 -28
- data/lib/Ruby/concurrently/proc/evaluation/error.rb +10 -0
- data/lib/all/concurrently/error.rb +0 -3
- data/lib/all/concurrently/evaluation.rb +8 -12
- data/lib/all/concurrently/event_loop.rb +1 -1
- data/lib/all/concurrently/event_loop/fiber.rb +3 -3
- data/lib/all/concurrently/event_loop/io_selector.rb +1 -1
- data/lib/all/concurrently/event_loop/run_queue.rb +29 -17
- data/lib/all/concurrently/proc.rb +13 -13
- data/lib/all/concurrently/proc/evaluation.rb +29 -29
- data/lib/all/concurrently/proc/evaluation/error.rb +13 -0
- data/lib/all/concurrently/proc/fiber.rb +3 -6
- data/lib/all/concurrently/version.rb +1 -1
- data/lib/all/io.rb +118 -41
- data/lib/all/kernel.rb +82 -29
- data/lib/mruby/concurrently/event_loop/io_selector.rb +46 -0
- data/lib/mruby/kernel.rb +1 -1
- data/mrbgem.rake +28 -17
- data/mruby_builds/build_config.rb +67 -0
- data/perf/Ruby/stage.rb +23 -0
- data/perf/benchmark_call_methods.rb +32 -0
- data/perf/benchmark_call_methods_waiting.rb +52 -0
- data/perf/benchmark_wait_methods.rb +38 -0
- data/perf/mruby/stage.rb +8 -0
- data/perf/profile_await_readable.rb +10 -0
- data/perf/{concurrent_proc_call.rb → profile_call.rb} +1 -5
- data/perf/{concurrent_proc_call_and_forget.rb → profile_call_and_forget.rb} +1 -5
- data/perf/{concurrent_proc_call_detached.rb → profile_call_detached.rb} +1 -5
- data/perf/{concurrent_proc_call_nonblock.rb → profile_call_nonblock.rb} +1 -5
- data/perf/profile_wait.rb +7 -0
- data/perf/stage.rb +47 -0
- data/perf/stage/benchmark.rb +47 -0
- data/perf/stage/benchmark/code_gen.rb +29 -0
- data/perf/stage/benchmark/code_gen/batch.rb +41 -0
- data/perf/stage/benchmark/code_gen/single.rb +38 -0
- metadata +27 -23
- data/ext/mruby/array.rb +0 -19
- data/lib/Ruby/concurrently/error.rb +0 -4
- data/perf/_shared/stage.rb +0 -33
- data/perf/concurrent_proc_calls.rb +0 -49
- data/perf/concurrent_proc_calls_awaiting.rb +0 -48
@@ -0,0 +1,46 @@
|
|
1
|
+
module Concurrently
|
2
|
+
# @private
|
3
|
+
class EventLoop::IOSelector
|
4
|
+
def initialize(event_loop)
|
5
|
+
@run_queue = event_loop.run_queue
|
6
|
+
@poll = Poll.new
|
7
|
+
@fds = {}
|
8
|
+
@evaluations = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def awaiting?
|
12
|
+
@evaluations.size > 0
|
13
|
+
end
|
14
|
+
|
15
|
+
def await_reader(io, evaluation)
|
16
|
+
fd = @poll.add(io, Poll::In)
|
17
|
+
@fds.store io, fd
|
18
|
+
@evaluations.store fd, evaluation
|
19
|
+
end
|
20
|
+
|
21
|
+
def await_writer(io, evaluation)
|
22
|
+
fd = @poll.add(io, Poll::Out)
|
23
|
+
@fds.store io, fd
|
24
|
+
@evaluations.store fd, evaluation
|
25
|
+
end
|
26
|
+
|
27
|
+
def cancel_reader(io)
|
28
|
+
fd = @fds.delete io
|
29
|
+
@poll.remove fd
|
30
|
+
@evaluations.delete fd
|
31
|
+
end
|
32
|
+
|
33
|
+
def cancel_writer(io)
|
34
|
+
fd = @fds.delete io
|
35
|
+
@poll.remove fd
|
36
|
+
@evaluations.delete fd
|
37
|
+
end
|
38
|
+
|
39
|
+
def process_ready_in(waiting_time)
|
40
|
+
waiting_time = -1 if waiting_time == Float::INFINITY
|
41
|
+
if ready = @poll.wait(waiting_time)
|
42
|
+
ready.each{ |fd| @run_queue.resume_evaluation! @evaluations[fd], true }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/mruby/kernel.rb
CHANGED
@@ -4,7 +4,7 @@ module Kernel
|
|
4
4
|
# Reimplements Kernel#concurrently. mruby does not support Proc.new without
|
5
5
|
# a block.
|
6
6
|
private def concurrently(*args, &block)
|
7
|
-
Concurrently::Proc.new(&block).
|
7
|
+
Concurrently::Proc.new(&block).call_detached *args
|
8
8
|
end
|
9
9
|
|
10
10
|
# Reimplements Kernel#concurrent_proc. mruby does not support Proc.new without
|
data/mrbgem.rake
CHANGED
@@ -3,19 +3,23 @@ require_relative 'lib/all/concurrently/version'
|
|
3
3
|
MRuby::Gem::Specification.new('mruby-concurrently') do |spec|
|
4
4
|
spec.version = Concurrently::VERSION
|
5
5
|
spec.summary = %q{A concurrency framework based on fibers}
|
6
|
-
spec.description =
|
7
|
-
Concurrently is a concurrency framework for Ruby and mruby
|
8
|
-
code can be
|
6
|
+
spec.description = <<'DESC'
|
7
|
+
Concurrently is a concurrency framework for Ruby and mruby based on
|
8
|
+
fibers. With it code can be evaluated independently in its own execution
|
9
|
+
context similar to a thread:
|
9
10
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
11
|
+
hello = concurrently do
|
12
|
+
wait 0.2 # seconds
|
13
|
+
"hello"
|
14
|
+
end
|
15
|
+
|
16
|
+
world = concurrently do
|
17
|
+
wait 0.1 # seconds
|
18
|
+
"world"
|
19
|
+
end
|
20
|
+
|
21
|
+
puts "#{hello.await_result} #{world.await_result}"
|
22
|
+
DESC
|
19
23
|
|
20
24
|
spec.homepage = "https://github.com/christopheraue/m-ruby-concurrently"
|
21
25
|
spec.license = 'Apache-2.0'
|
@@ -32,11 +36,18 @@ evaluations).
|
|
32
36
|
Dir["#{spec.dir}/lib/mruby/**/*.rb"].sort
|
33
37
|
spec.test_rbfiles = Dir["#{spec.dir}/test/mruby/*.rb"]
|
34
38
|
|
35
|
-
spec.add_dependency 'mruby-array-ext'
|
36
|
-
spec.add_dependency 'mruby-numeric-ext'
|
37
|
-
spec.add_dependency 'mruby-enumerator'
|
38
|
-
spec.add_dependency 'mruby-fiber'
|
39
|
-
spec.add_dependency 'mruby-time'
|
39
|
+
spec.add_dependency 'mruby-array-ext', :core => 'mruby-array-ext'
|
40
|
+
spec.add_dependency 'mruby-numeric-ext', :core => 'mruby-numeric-ext'
|
41
|
+
spec.add_dependency 'mruby-enumerator', :core => 'mruby-enumerator'
|
42
|
+
spec.add_dependency 'mruby-fiber', :core => 'mruby-fiber'
|
43
|
+
spec.add_dependency 'mruby-time', :core => 'mruby-time'
|
40
44
|
spec.add_dependency 'mruby-io'
|
41
45
|
spec.add_dependency 'mruby-callbacks_attachable', '~> 2.2', github: 'christopheraue/m-ruby-callbacks_attachable'
|
46
|
+
|
47
|
+
# use mruby-poll only on unix-like OSes
|
48
|
+
if ENV['VisualStudioVersion'] || ENV['VSINSTALLDIR']
|
49
|
+
spec.rbfiles.delete "#{spec.dir}/lib/mruby/concurrently/event_loop/io_selector.rb"
|
50
|
+
else
|
51
|
+
spec.add_dependency 'mruby-poll'
|
52
|
+
end
|
42
53
|
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
MRuby::Build.new do
|
2
|
+
if ENV['VisualStudioVersion'] || ENV['VSINSTALLDIR']
|
3
|
+
toolchain :visualcpp
|
4
|
+
else
|
5
|
+
toolchain :gcc
|
6
|
+
end
|
7
|
+
|
8
|
+
enable_debug
|
9
|
+
end
|
10
|
+
|
11
|
+
MRuby::Build.new 'test', File.dirname(__FILE__) do
|
12
|
+
self.gem_clone_dir = "#{MRUBY_ROOT}/build/mrbgems"
|
13
|
+
|
14
|
+
if ENV['VisualStudioVersion'] || ENV['VSINSTALLDIR']
|
15
|
+
toolchain :visualcpp
|
16
|
+
else
|
17
|
+
toolchain :gcc
|
18
|
+
end
|
19
|
+
|
20
|
+
enable_test
|
21
|
+
gem File.expand_path File.dirname File.dirname __FILE__
|
22
|
+
end
|
23
|
+
|
24
|
+
MRuby::Build.new 'benchmark', File.dirname(__FILE__) do
|
25
|
+
self.gem_clone_dir = "#{MRUBY_ROOT}/build/mrbgems"
|
26
|
+
|
27
|
+
if ENV['VisualStudioVersion'] || ENV['VSINSTALLDIR']
|
28
|
+
toolchain :visualcpp
|
29
|
+
else
|
30
|
+
toolchain :gcc
|
31
|
+
cc.flags << '-O3'
|
32
|
+
end
|
33
|
+
|
34
|
+
gem core: 'mruby-bin-mruby'
|
35
|
+
gem core: 'mruby-bin-mirb'
|
36
|
+
gem core: 'mruby-proc-ext'
|
37
|
+
gem core: 'mruby-sprintf'
|
38
|
+
gem core: 'mruby-eval'
|
39
|
+
|
40
|
+
gem_dir = File.expand_path File.dirname File.dirname __FILE__
|
41
|
+
gem gem_dir do |gem|
|
42
|
+
gem.rbfiles += Dir["#{gem_dir}/perf/{stage.rb,stage/**/*.rb}"]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
MRuby::Build.new 'profile', File.dirname(__FILE__) do
|
47
|
+
self.gem_clone_dir = "#{MRUBY_ROOT}/build/mrbgems"
|
48
|
+
|
49
|
+
if ENV['VisualStudioVersion'] || ENV['VSINSTALLDIR']
|
50
|
+
toolchain :visualcpp
|
51
|
+
else
|
52
|
+
toolchain :gcc
|
53
|
+
end
|
54
|
+
|
55
|
+
enable_debug
|
56
|
+
cc.defines = %w(MRB_ENABLE_DEBUG_HOOK)
|
57
|
+
|
58
|
+
gem github: 'miura1729/mruby-profiler'
|
59
|
+
gem core: 'mruby-bin-mruby'
|
60
|
+
gem core: 'mruby-proc-ext'
|
61
|
+
gem core: 'mruby-sprintf'
|
62
|
+
|
63
|
+
gem_dir = File.expand_path File.dirname File.dirname __FILE__
|
64
|
+
gem gem_dir do |gem|
|
65
|
+
gem.rbfiles << "#{gem_dir}/perf/stage.rb" << "#{gem_dir}/perf/mruby/stage.rb"
|
66
|
+
end
|
67
|
+
end
|
data/perf/Ruby/stage.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
|
3
|
+
Bundler.require :default
|
4
|
+
Bundler.require :perf
|
5
|
+
|
6
|
+
perf = File.dirname File.dirname __FILE__
|
7
|
+
Dir["#{perf}/{stage.rb,stage/**/*.rb}"].sort.each{ |f| require f }
|
8
|
+
|
9
|
+
class Stage
|
10
|
+
def profile(seconds: 1, printer: 'flat')
|
11
|
+
gc_disabled do
|
12
|
+
profile = RubyProf::Profile.new(merge_fibers: true).tap(&:start)
|
13
|
+
|
14
|
+
result = execute(seconds: seconds){ yield }
|
15
|
+
|
16
|
+
printer[0] = printer[0].capitalize
|
17
|
+
RubyProf.const_get("#{printer}Printer").new(profile.stop).print(STDOUT, sort_method: :self_time)
|
18
|
+
|
19
|
+
result
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
@@ -0,0 +1,32 @@
|
|
1
|
+
stage = Stage.new
|
2
|
+
batch_size = ARGV.fetch(0, 1).to_i
|
3
|
+
print_results_only = ARGV[1] == 'skip_header'
|
4
|
+
|
5
|
+
stage.benchmark 'proc.call',
|
6
|
+
batch_size: batch_size,
|
7
|
+
proc: "proc{}",
|
8
|
+
call: :call
|
9
|
+
|
10
|
+
stage.benchmark 'conproc.call',
|
11
|
+
batch_size: batch_size,
|
12
|
+
proc: "concurrent_proc{}",
|
13
|
+
call: :call
|
14
|
+
|
15
|
+
stage.benchmark 'conproc.call_nonblock',
|
16
|
+
batch_size: batch_size,
|
17
|
+
proc: "concurrent_proc{}",
|
18
|
+
call: :call_nonblock
|
19
|
+
|
20
|
+
stage.benchmark 'conproc.call_detached',
|
21
|
+
batch_size: batch_size,
|
22
|
+
proc: "concurrent_proc{}",
|
23
|
+
call: :call_detached,
|
24
|
+
sync: :wait
|
25
|
+
|
26
|
+
stage.benchmark 'conproc.call_and_forget',
|
27
|
+
batch_size: batch_size,
|
28
|
+
proc: "concurrent_proc{}",
|
29
|
+
call: :call_and_forget,
|
30
|
+
sync: :wait
|
31
|
+
|
32
|
+
stage.perform print_results_only
|
@@ -0,0 +1,52 @@
|
|
1
|
+
stage = Stage.new
|
2
|
+
batch_size = ARGV.fetch(0, 1).to_i
|
3
|
+
print_results_only = ARGV[1] == 'skip_header'
|
4
|
+
|
5
|
+
stage.benchmark 'call',
|
6
|
+
batch_size: batch_size,
|
7
|
+
proc: "concurrent_proc{}",
|
8
|
+
call: :call,
|
9
|
+
sync: true
|
10
|
+
|
11
|
+
stage.benchmark 'call_nonblock',
|
12
|
+
batch_size: batch_size,
|
13
|
+
proc: "concurrent_proc{}",
|
14
|
+
call: :call_nonblock
|
15
|
+
|
16
|
+
stage.benchmark 'call_detached',
|
17
|
+
batch_size: batch_size,
|
18
|
+
proc: "concurrent_proc{}",
|
19
|
+
call: :call_detached,
|
20
|
+
sync: true
|
21
|
+
|
22
|
+
stage.benchmark 'call_and_forget',
|
23
|
+
batch_size: batch_size,
|
24
|
+
proc: "concurrent_proc{}",
|
25
|
+
call: :call_and_forget,
|
26
|
+
sync: true
|
27
|
+
|
28
|
+
stage.benchmark 'waiting call',
|
29
|
+
batch_size: batch_size,
|
30
|
+
proc: "concurrent_proc{ wait 0 }",
|
31
|
+
call: :call,
|
32
|
+
sync: true
|
33
|
+
|
34
|
+
stage.benchmark 'waiting call_nonblock',
|
35
|
+
batch_size: batch_size,
|
36
|
+
proc: "concurrent_proc{ wait 0 }",
|
37
|
+
call: :call_nonblock,
|
38
|
+
sync: true
|
39
|
+
|
40
|
+
stage.benchmark 'waiting call_detached',
|
41
|
+
batch_size: batch_size,
|
42
|
+
proc: "concurrent_proc{ wait 0 }",
|
43
|
+
call: :call_detached,
|
44
|
+
sync: true
|
45
|
+
|
46
|
+
stage.benchmark 'waiting call_and_forget',
|
47
|
+
batch_size: batch_size,
|
48
|
+
proc: "concurrent_proc{ wait 0 }",
|
49
|
+
call: :call_and_forget,
|
50
|
+
sync: true
|
51
|
+
|
52
|
+
stage.perform print_results_only
|
@@ -0,0 +1,38 @@
|
|
1
|
+
stage = Stage.new
|
2
|
+
batch_size = ARGV.fetch(0, 1).to_i
|
3
|
+
print_results_only = ARGV[1] == 'skip_header'
|
4
|
+
|
5
|
+
stage.benchmark :wait,
|
6
|
+
batch_size: batch_size,
|
7
|
+
proc: <<RUBY, call: :call
|
8
|
+
proc do
|
9
|
+
wait 0 # schedule the proc be resumed ASAP
|
10
|
+
end
|
11
|
+
RUBY
|
12
|
+
|
13
|
+
stage.benchmark "await_readable",
|
14
|
+
batch_size: batch_size,
|
15
|
+
proc: <<RUBY, call: :call, args: <<RUBY
|
16
|
+
proc do |r,w|
|
17
|
+
r.await_readable
|
18
|
+
end
|
19
|
+
RUBY
|
20
|
+
IO.pipe.tap{ |r,w| w.write '0' }
|
21
|
+
RUBY
|
22
|
+
|
23
|
+
stage.benchmark "await_writable",
|
24
|
+
batch_size: batch_size,
|
25
|
+
proc: <<RUBY, call: :call, args: <<RUBY
|
26
|
+
proc do |r,w|
|
27
|
+
w.await_writable
|
28
|
+
end
|
29
|
+
RUBY
|
30
|
+
IO.pipe
|
31
|
+
RUBY
|
32
|
+
|
33
|
+
# Warm up
|
34
|
+
wait_proc = concurrent_proc{ wait 0 }
|
35
|
+
stage.execute{ wait_proc.call }
|
36
|
+
|
37
|
+
# Performance
|
38
|
+
stage.perform print_results_only
|
data/perf/mruby/stage.rb
ADDED
@@ -1,13 +1,9 @@
|
|
1
|
-
#!/bin/env ruby
|
2
|
-
|
3
|
-
require_relative "_shared/stage"
|
4
|
-
|
5
1
|
stage = Stage.new
|
6
2
|
|
7
3
|
evaluation = Concurrently::Evaluation.current
|
8
4
|
conproc = concurrent_proc{ evaluation.resume! }
|
9
5
|
|
10
|
-
result = stage.
|
6
|
+
result = stage.profile(seconds: 1) do
|
11
7
|
conproc.call_and_forget
|
12
8
|
await_resume!
|
13
9
|
end
|
@@ -1,13 +1,9 @@
|
|
1
|
-
#!/bin/env ruby
|
2
|
-
|
3
|
-
require_relative "_shared/stage"
|
4
|
-
|
5
1
|
stage = Stage.new
|
6
2
|
|
7
3
|
evaluation = Concurrently::Evaluation.current
|
8
4
|
conproc = concurrent_proc{ evaluation.resume! }
|
9
5
|
|
10
|
-
result = stage.
|
6
|
+
result = stage.profile(seconds: 1) do
|
11
7
|
conproc.call_detached
|
12
8
|
await_resume!
|
13
9
|
end
|
data/perf/stage.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
class Stage
|
2
|
+
def initialize
|
3
|
+
@benchmarks = []
|
4
|
+
end
|
5
|
+
|
6
|
+
def gc_disabled
|
7
|
+
GC.start
|
8
|
+
GC.disable
|
9
|
+
yield
|
10
|
+
ensure
|
11
|
+
GC.enable
|
12
|
+
end
|
13
|
+
|
14
|
+
def execute(opts = {})
|
15
|
+
seconds = opts[:seconds] || 1
|
16
|
+
event_loop = Concurrently::EventLoop.current
|
17
|
+
event_loop.reinitialize!
|
18
|
+
iterations = 0
|
19
|
+
start_time = event_loop.lifetime
|
20
|
+
end_time = start_time + seconds
|
21
|
+
while event_loop.lifetime < end_time
|
22
|
+
yield
|
23
|
+
iterations += 1
|
24
|
+
end
|
25
|
+
stop_time = event_loop.lifetime
|
26
|
+
{ iterations: iterations, time: (stop_time-start_time) }
|
27
|
+
end
|
28
|
+
|
29
|
+
def benchmark(*args)
|
30
|
+
@benchmarks << Benchmark.new(self, *args)
|
31
|
+
end
|
32
|
+
|
33
|
+
def perform(print_results_only = false)
|
34
|
+
if @benchmarks.size
|
35
|
+
unless print_results_only
|
36
|
+
puts Benchmark.header
|
37
|
+
@benchmarks.each do |b|
|
38
|
+
puts b.desc
|
39
|
+
end
|
40
|
+
end
|
41
|
+
puts Benchmark.result_header
|
42
|
+
@benchmarks.each{ |b| b.run }
|
43
|
+
puts
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|