parallelize 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+ group :development do
9
+ gem "yard", "~> 0.6.0"
10
+ gem "bundler", "~> 1.0.0"
11
+ gem "jeweler", "~> 1.6.4"
12
+ gem "rcov", ">= 0"
13
+ end
@@ -0,0 +1,22 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ git (1.2.5)
5
+ jeweler (1.6.4)
6
+ bundler (~> 1.0)
7
+ git (>= 1.2.5)
8
+ rake
9
+ rake (0.9.2)
10
+ rcov (0.9.9)
11
+ rcov (0.9.9-java)
12
+ yard (0.6.8)
13
+
14
+ PLATFORMS
15
+ java
16
+ ruby
17
+
18
+ DEPENDENCIES
19
+ bundler (~> 1.0.0)
20
+ jeweler (~> 1.6.4)
21
+ rcov
22
+ yard (~> 0.6.0)
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Junegunn Choi
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,52 @@
1
+ = parallelize
2
+
3
+ Simple multi-threading for Ruby.
4
+
5
+ == Installation
6
+ gem install parallelize
7
+
8
+ == Examples
9
+ require 'rubygems'
10
+ require 'parallelize'
11
+
12
+ parallelize(4) do
13
+ puts "I'm a thread"
14
+
15
+ # ...
16
+ end
17
+
18
+ # Zero-based thread index
19
+ parallelize(4) do |thread_idx|
20
+ puts "I'm thread ##{thread_idx}"
21
+
22
+ # ...
23
+ end
24
+
25
+ # Enumerable#peach
26
+ (0..100).peach(4) do |elem, thread_idx|
27
+ puts "Thread ##{thread_idx} processing #{elem}"
28
+ end
29
+
30
+ === Collecting exceptions
31
+ begin
32
+ parallelize(4, true) do |elem, thread_idx|
33
+ # Each thread can complete its block even when some other threads throw exceptions
34
+ end
35
+ rescue ParallelException => e
36
+ p e.exceptions
37
+ end
38
+
39
+ == Contributing to parallelize
40
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
41
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
42
+ * Fork the project
43
+ * Start a feature/bugfix branch
44
+ * Commit and push until you are happy with your contribution
45
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
46
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
47
+
48
+ == Copyright
49
+
50
+ Copyright (c) 2011 Junegunn Choi. See LICENSE.txt for
51
+ further details.
52
+
@@ -0,0 +1,46 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "parallelize"
18
+ gem.homepage = "http://github.com/junegunn/parallelize"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{Simple multi-threading for Ruby}
21
+ gem.description = %Q{Simple multi-threading for Ruby}
22
+ gem.email = "junegunn.c@gmail.com"
23
+ gem.authors = ["Junegunn Choi"]
24
+ # dependencies defined in Gemfile
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
27
+
28
+ require 'rake/testtask'
29
+ Rake::TestTask.new(:test) do |test|
30
+ test.libs << 'lib' << 'test'
31
+ test.pattern = 'test/**/test_*.rb'
32
+ test.verbose = true
33
+ end
34
+
35
+ require 'rcov/rcovtask'
36
+ Rcov::RcovTask.new do |test|
37
+ test.libs << 'test'
38
+ test.pattern = 'test/**/test_*.rb'
39
+ test.verbose = true
40
+ test.rcov_opts << '--exclude "gems/*"'
41
+ end
42
+
43
+ task :default => :test
44
+
45
+ require 'yard'
46
+ YARD::Rake::YardocTask.new
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.1
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'parallelize'
5
+
6
+ parallelize(4) do
7
+ puts "I'm a thread"
8
+ end
9
+
10
+ parallelize(4) do |thread_idx|
11
+ puts "I'm thread ##{thread_idx}" # thread_idx is zero-based
12
+ # ...
13
+ end
14
+
15
+ # Enumerable#peach
16
+ (0..100).peach(4) do |elem, thread_idx|
17
+ puts "Thread ##{thread_idx} processing #{elem}"
18
+ end
19
+
20
+ begin
21
+ parallelize(4, true) do |elem, thread_idx|
22
+ # Each thread can complete its block even when some other threads throw exceptions
23
+ raise Exception.new(thread_idx) if thread_idx < 2
24
+ sleep 3
25
+ end
26
+ rescue ParallelException => e
27
+ p e.exceptions
28
+ end
29
+
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'parallelize'
5
+
6
+ TOTAL_SIZE = 1_0000_0000
7
+ SLICE_SIZE = 100000
8
+
9
+ parallelize(4) do |thread_idx|
10
+ cnt = 0
11
+ sst = Time.now
12
+ (0...TOTAL_SIZE).each_slice(SLICE_SIZE) do |slice|
13
+ st = Time.now
14
+ slice.each do |i|
15
+ record = {}
16
+ record['userid'] = rand(100_0000_0000).to_s(36)
17
+ record['content'] = '_' * (50 + rand(100))
18
+ record['reg_dttm'] = Time.now
19
+
20
+ cnt += 1
21
+ end
22
+ elapsed = Time.now - st
23
+ total_elapsed = Time.now - sst
24
+ puts "#{thread_idx}: #{cnt} records upserted. #{"%.2f" % (SLICE_SIZE / elapsed)} records/sec. #{"%.2f" % (cnt / total_elapsed)} records/sec"
25
+ end
26
+ end
@@ -0,0 +1,11 @@
1
+ require 'parallelize/parallel_exception'
2
+ require 'parallelize/enumerable_ext'
3
+
4
+ # Execute the given block with multiple threads.
5
+ # @return [Array] Threads.
6
+ # @param [Fixnum] num_threads Number of concurrent threads
7
+ # @param [Boolean] collect_exceptions If true, waits for all threads to complete even in case of exception, and throws ParallelException at the end. If false exception is immediately thrown.
8
+ def parallelize num_threads, collect_exceptions = false, &block
9
+ num_threads.times.map.peach(num_threads, collect_exceptions, &block)
10
+ end
11
+
@@ -0,0 +1,53 @@
1
+ module Enumerable
2
+ # Divides the Enumerable objects into pieces and execute with multiple threads
3
+ # @return [Array] Threads.
4
+ # @param [Fixnum] num_threads Number of concurrent threads
5
+ # @param [Boolean] collect_exceptions If true, waits for all threads to complete even in case of exception, and throws ParallelException at the end. If false exception is immediately thrown.
6
+ def peach num_threads, collect_exceptions = false, &block
7
+ raise ArgumentError.new("Block not given") unless block_given?
8
+ raise ArgumentError.new("Invalid number of threads") if num_threads < 1
9
+
10
+ threads = []
11
+ self.each_slice((self.count{true} / num_threads.to_f).ceil) do |slice|
12
+ threads <<
13
+ case block.arity
14
+ when 2
15
+ Thread.new(slice, threads.length) { |my_slice, thread_idx|
16
+ my_slice.each { |e| yield e, thread_idx }
17
+ }
18
+ when 1
19
+ Thread.new(slice) { |my_slice|
20
+ my_slice.each { |e| yield e }
21
+ }
22
+ when 0, -1
23
+ raise ArgumentError.new("Invalid arity: #{block.arity}") if
24
+ RUBY_VERSION !~ /^1.8\./ && block.arity == -1
25
+ Thread.new(slice) { |my_slice|
26
+ my_slice.each { yield }
27
+ }
28
+ else
29
+ raise ArgumentError.new("Invalid arity: #{block.arity}")
30
+ end
31
+ end
32
+
33
+ exceptions = {}
34
+ threads.each_with_index do |thr, idx|
35
+ begin
36
+ thr.join
37
+ rescue Exception => e
38
+ if collect_exceptions
39
+ exceptions[idx] = e
40
+ else
41
+ raise e
42
+ end
43
+ end
44
+ end
45
+
46
+ if exceptions.empty?
47
+ threads
48
+ else
49
+ raise ParallelException.new(exceptions)
50
+ end
51
+ end
52
+ end
53
+
@@ -0,0 +1,14 @@
1
+ class ParallelException < Exception
2
+ # @return [Hash] Hash of exceptions thrown. Indexed by thread index.
3
+ attr_reader :exceptions
4
+
5
+ def initialize(exceptions)
6
+ @exceptions = exceptions
7
+ end
8
+
9
+ def to_s
10
+ "Exceptions thrown during parallel execution: [#{@exceptions.inspect}]"
11
+ end
12
+ end
13
+
14
+
@@ -0,0 +1,17 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'test/unit'
11
+
12
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
13
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
14
+ require 'parallelize'
15
+
16
+ class Test::Unit::TestCase
17
+ end
@@ -0,0 +1,130 @@
1
+ require 'helper'
2
+ require 'thread'
3
+
4
+ class TestParallelize < Test::Unit::TestCase
5
+ def test_parallelize
6
+ num_threads = 4
7
+
8
+ m = Mutex.new
9
+ cnt = 0
10
+ threads = parallelize(num_threads) do
11
+ m.synchronize do
12
+ cnt += 1
13
+ end
14
+ end
15
+
16
+ assert_equal num_threads, threads.length
17
+ assert_equal num_threads, cnt
18
+ assert threads.all? { |t| t.is_a? Thread }
19
+ end
20
+
21
+ def test_parallelize_thread_idx
22
+ num_threads = 4
23
+
24
+ m = Mutex.new
25
+ max_thread_idx = 0
26
+ threads = parallelize(num_threads) do |thread_idx|
27
+ m.synchronize do
28
+ max_thread_idx = [thread_idx, max_thread_idx].max
29
+ end
30
+ end
31
+ assert_equal num_threads - 1, max_thread_idx
32
+ assert_equal num_threads, threads.length
33
+ assert threads.all? { |t| t.is_a? Thread }
34
+ end
35
+
36
+ def test_parallelize_exception
37
+ num_threads = 4
38
+ delay = 3
39
+
40
+ [true, false].each do |collect_exception|
41
+ st = Time.now
42
+ begin
43
+ threads = parallelize(num_threads, collect_exception) do |thread_idx|
44
+ unknown_method_should_fail if [0, 1].include? thread_idx
45
+ sleep delay
46
+ end
47
+ rescue Exception => e
48
+ ex = e
49
+ end
50
+
51
+ if collect_exception
52
+ assert_equal ParallelException, ex.class
53
+ assert_equal Hash, ex.exceptions.class
54
+ assert_equal 2, ex.exceptions.length
55
+ assert_equal [0, 1], ex.exceptions.keys
56
+ assert Time.now - st >= delay, "Did not collect exceptions"
57
+ p e.exceptions
58
+ else
59
+ assert_equal NameError, ex.class
60
+ assert Time.now - st < delay, "Did not return immediately"
61
+ end
62
+ end
63
+ end
64
+
65
+ def test_peach
66
+ count = 110
67
+ num_threads = 4
68
+
69
+ m = Mutex.new
70
+ r = Hash.new { |h, k| h[k] = [] }
71
+ range = (0...count)
72
+ range.peach(num_threads) do |elem, thread_idx|
73
+ m.synchronize { r[thread_idx] << elem }
74
+ end
75
+
76
+ assert_equal count, r.values.inject(0) { |sum, arr| sum + arr.length }
77
+ assert_equal range.to_a.sort, r.values.inject([]) { |cc, arr| cc += arr; cc }.sort
78
+ end
79
+
80
+ def test_peach_exception
81
+ count = 110
82
+ num_threads = 4
83
+
84
+ m = Mutex.new
85
+ [true, false].each do |collect_exception|
86
+ r = Hash.new { |h, k| h[k] = [] }
87
+ range = (0...count)
88
+ begin
89
+ range.peach(num_threads, collect_exception) do |elem, thread_idx|
90
+ unknown_method_should_fail if [0, 1].include? thread_idx
91
+ m.synchronize { r[thread_idx] << elem }
92
+ end
93
+ rescue Exception => e
94
+ ex = e
95
+ end
96
+
97
+ if collect_exception
98
+ assert_equal ParallelException, ex.class
99
+ assert_equal Hash, ex.exceptions.class
100
+ assert_equal 2, ex.exceptions.length
101
+ assert_equal [0, 1], ex.exceptions.keys
102
+ assert r.values.inject(0) { |sum, arr| sum + arr.length } < count
103
+ assert r.values.inject(0) { |sum, arr| sum + arr.length } > count / num_threads
104
+ p e.exceptions
105
+ else
106
+ assert_equal NameError, ex.class
107
+ assert r.values.inject(0) { |sum, arr| sum + arr.length } < count
108
+ end
109
+ end
110
+ end
111
+
112
+ def test_peach_invalid_arity_block
113
+ assert_raise(ArgumentError) {
114
+ (0..100).peach(4) do |a, b, c|
115
+
116
+ end
117
+ }
118
+ assert_raise(ArgumentError) {
119
+ (0..100).peach(4)
120
+ }
121
+ assert_raise(ArgumentError) {
122
+ (0..100).peach(0) do |a|
123
+ end
124
+ }
125
+
126
+ # Should be ok
127
+ (0..100).peach(4) do
128
+ end
129
+ end
130
+ end
metadata ADDED
@@ -0,0 +1,114 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: parallelize
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.2.1
6
+ platform: ruby
7
+ authors:
8
+ - Junegunn Choi
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-08-02 00:00:00 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: yard
17
+ version_requirements: &id001 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ~>
21
+ - !ruby/object:Gem::Version
22
+ version: 0.6.0
23
+ requirement: *id001
24
+ prerelease: false
25
+ type: :development
26
+ - !ruby/object:Gem::Dependency
27
+ name: bundler
28
+ version_requirements: &id002 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: 1.0.0
34
+ requirement: *id002
35
+ prerelease: false
36
+ type: :development
37
+ - !ruby/object:Gem::Dependency
38
+ name: jeweler
39
+ version_requirements: &id003 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ~>
43
+ - !ruby/object:Gem::Version
44
+ version: 1.6.4
45
+ requirement: *id003
46
+ prerelease: false
47
+ type: :development
48
+ - !ruby/object:Gem::Dependency
49
+ name: rcov
50
+ version_requirements: &id004 !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: "0"
56
+ requirement: *id004
57
+ prerelease: false
58
+ type: :development
59
+ description: Simple multi-threading for Ruby
60
+ email: junegunn.c@gmail.com
61
+ executables: []
62
+
63
+ extensions: []
64
+
65
+ extra_rdoc_files:
66
+ - LICENSE.txt
67
+ - README.rdoc
68
+ files:
69
+ - .document
70
+ - Gemfile
71
+ - Gemfile.lock
72
+ - LICENSE.txt
73
+ - README.rdoc
74
+ - Rakefile
75
+ - VERSION
76
+ - example/example.rb
77
+ - example/example2.rb
78
+ - lib/parallelize.rb
79
+ - lib/parallelize/enumerable_ext.rb
80
+ - lib/parallelize/parallel_exception.rb
81
+ - test/helper.rb
82
+ - test/test_parallelize.rb
83
+ homepage: http://github.com/junegunn/parallelize
84
+ licenses:
85
+ - MIT
86
+ post_install_message:
87
+ rdoc_options: []
88
+
89
+ require_paths:
90
+ - lib
91
+ required_ruby_version: !ruby/object:Gem::Requirement
92
+ none: false
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ hash: 2
97
+ segments:
98
+ - 0
99
+ version: "0"
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ none: false
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ version: "0"
106
+ requirements: []
107
+
108
+ rubyforge_project:
109
+ rubygems_version: 1.8.6
110
+ signing_key:
111
+ specification_version: 3
112
+ summary: Simple multi-threading for Ruby
113
+ test_files: []
114
+