concurrently 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/.travis.yml +8 -3
  4. data/README.md +70 -60
  5. data/RELEASE_NOTES.md +16 -1
  6. data/Rakefile +98 -14
  7. data/concurrently.gemspec +16 -12
  8. data/ext/mruby/io.rb +1 -1
  9. data/guides/Overview.md +191 -66
  10. data/guides/Performance.md +300 -102
  11. data/guides/Troubleshooting.md +28 -28
  12. data/lib/Ruby/concurrently/proc/evaluation/error.rb +10 -0
  13. data/lib/all/concurrently/error.rb +0 -3
  14. data/lib/all/concurrently/evaluation.rb +8 -12
  15. data/lib/all/concurrently/event_loop.rb +1 -1
  16. data/lib/all/concurrently/event_loop/fiber.rb +3 -3
  17. data/lib/all/concurrently/event_loop/io_selector.rb +1 -1
  18. data/lib/all/concurrently/event_loop/run_queue.rb +29 -17
  19. data/lib/all/concurrently/proc.rb +13 -13
  20. data/lib/all/concurrently/proc/evaluation.rb +29 -29
  21. data/lib/all/concurrently/proc/evaluation/error.rb +13 -0
  22. data/lib/all/concurrently/proc/fiber.rb +3 -6
  23. data/lib/all/concurrently/version.rb +1 -1
  24. data/lib/all/io.rb +118 -41
  25. data/lib/all/kernel.rb +82 -29
  26. data/lib/mruby/concurrently/event_loop/io_selector.rb +46 -0
  27. data/lib/mruby/kernel.rb +1 -1
  28. data/mrbgem.rake +28 -17
  29. data/mruby_builds/build_config.rb +67 -0
  30. data/perf/Ruby/stage.rb +23 -0
  31. data/perf/benchmark_call_methods.rb +32 -0
  32. data/perf/benchmark_call_methods_waiting.rb +52 -0
  33. data/perf/benchmark_wait_methods.rb +38 -0
  34. data/perf/mruby/stage.rb +8 -0
  35. data/perf/profile_await_readable.rb +10 -0
  36. data/perf/{concurrent_proc_call.rb → profile_call.rb} +1 -5
  37. data/perf/{concurrent_proc_call_and_forget.rb → profile_call_and_forget.rb} +1 -5
  38. data/perf/{concurrent_proc_call_detached.rb → profile_call_detached.rb} +1 -5
  39. data/perf/{concurrent_proc_call_nonblock.rb → profile_call_nonblock.rb} +1 -5
  40. data/perf/profile_wait.rb +7 -0
  41. data/perf/stage.rb +47 -0
  42. data/perf/stage/benchmark.rb +47 -0
  43. data/perf/stage/benchmark/code_gen.rb +29 -0
  44. data/perf/stage/benchmark/code_gen/batch.rb +41 -0
  45. data/perf/stage/benchmark/code_gen/single.rb +38 -0
  46. metadata +27 -23
  47. data/ext/mruby/array.rb +0 -19
  48. data/lib/Ruby/concurrently/error.rb +0 -4
  49. data/perf/_shared/stage.rb +0 -33
  50. data/perf/concurrent_proc_calls.rb +0 -49
  51. 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).call_and_forget *args
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 = <<-DESC
7
- Concurrently is a concurrency framework for Ruby and mruby. With it, concurrent
8
- code can be written sequentially similar to async/await.
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
- The concurrency primitive of Concurrently is the concurrent proc. It is very
11
- similar to a regular proc. Calling a concurrent proc creates a concurrent
12
- evaluation which is kind of a lightweight thread: It can wait for stuff without
13
- blocking other concurrent evaluations.
14
-
15
- Under the hood, concurrent procs are evaluated inside fibers. They can wait for
16
- readiness of I/O or a period of time (or the result of other concurrent
17
- evaluations).
18
- DESC
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
@@ -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
@@ -0,0 +1,8 @@
1
+ class Stage
2
+ def profile(opts = {})
3
+ gc_disabled do
4
+ execute(opts){ yield }
5
+ end
6
+ end
7
+ end
8
+
@@ -0,0 +1,10 @@
1
+ stage = Stage.new
2
+
3
+ r,w = IO.pipe
4
+ w.write '0'
5
+
6
+ result = stage.profile(seconds: 1) do
7
+ r.await_readable
8
+ end
9
+
10
+ puts "#{result[:iterations]} executions in #{result[:time].round 4} seconds"
@@ -1,12 +1,8 @@
1
- #!/bin/env ruby
2
-
3
- require_relative "_shared/stage"
4
-
5
1
  stage = Stage.new
6
2
 
7
3
  conproc = concurrent_proc{}
8
4
 
9
- result = stage.measure(seconds: 1) do
5
+ result = stage.profile(seconds: 1) do
10
6
  conproc.call
11
7
  end
12
8
 
@@ -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.measure(seconds: 1) do
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.measure(seconds: 1) do
6
+ result = stage.profile(seconds: 1) do
11
7
  conproc.call_detached
12
8
  await_resume!
13
9
  end
@@ -1,12 +1,8 @@
1
- #!/bin/env ruby
2
-
3
- require_relative "_shared/stage"
4
-
5
1
  stage = Stage.new
6
2
 
7
3
  conproc = concurrent_proc{}
8
4
 
9
- result = stage.measure(seconds: 1) do
5
+ result = stage.profile(seconds: 1) do
10
6
  conproc.call_nonblock
11
7
  end
12
8
 
@@ -0,0 +1,7 @@
1
+ stage = Stage.new
2
+
3
+ result = stage.profile(seconds: 1) do
4
+ wait 0
5
+ end
6
+
7
+ puts "#{result[:iterations]} executions in #{result[:time].round 4} seconds"
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
+