concurrently 1.0.1 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|