parallel 0.3.1

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.
@@ -0,0 +1,63 @@
1
+ Run any kind of code in parallel Processes or Threads, to speedup computation by factor #{your_cpus} X.
2
+
3
+ - Child processes are killed when your main process is killed through Ctrl+c or kill -2
4
+
5
+ Install
6
+ =======
7
+ sudo gem install grosser-parallel -s http://gems.github.com/
8
+
9
+ Usage
10
+ =====
11
+ ### Processes
12
+ - Speedup through multiple CPUs
13
+ - Speedup for blocking operations
14
+ - Protects global data
15
+ - Extra memory used
16
+
17
+ ### Threads
18
+ - Speedup for blocking operations
19
+ - Global data can be modified
20
+ - No extra memory used
21
+
22
+ Map-Reduce-Style
23
+ # 2 CPUs -> finished after 2 runs (a,b + c)
24
+ results = Parallel.map(['a','b','c']) do |one_letter|
25
+ expensive_calculation(letter)
26
+ end
27
+
28
+ # 3 Processes -> finished after 1 run
29
+ results = Parallel.map(['a','b','c'], :in_processes=>3){|one_letter| ... }
30
+
31
+ # 3 Threads -> finished after 1 run
32
+ results = Parallel.map(['a','b','c'], :in_threads=>3){|one_letter| ... }
33
+
34
+
35
+ Normal
36
+ #i -> 0...number_of_your_cpus
37
+ results = Parallel.in_processes do |i|
38
+ expensive_computation(data[i])
39
+ end
40
+
41
+ #i -> 0...4
42
+ results = Parallel.in_processes(4) do |i|
43
+ expensive_computation(data[i])
44
+ end
45
+
46
+ # Threads
47
+ results = Parallel.in_threads(4) do |i|
48
+ blocking_computation(data[i])
49
+ end
50
+
51
+ TODO
52
+ ====
53
+ - JRuby / Windows support <-> possible ?
54
+
55
+ Authors
56
+ =======
57
+
58
+ ###Contributors (alphabetical)
59
+ - [TJ Holowaychuk](http://vision-media.ca/) -- tj<$at$>vision-media.ca
60
+
61
+ [Michael Grosser](http://pragmatig.wordpress.com)
62
+ grosser.michael@gmail.com
63
+ Hereby placed under public domain, do what you want, just do not hold me accountable...
@@ -0,0 +1,26 @@
1
+ desc "Run all specs in spec directory"
2
+ task :default do
3
+ options = "--colour --format progress --loadby --reverse"
4
+ files = FileList['spec/**/*_spec.rb']
5
+ system("spec #{options} #{files}")
6
+ end
7
+
8
+ # fake task so that rubyforge:release works
9
+ task :rdoc do
10
+ end
11
+
12
+ begin
13
+ require 'jeweler'
14
+ project_name = 'parallel'
15
+ Jeweler::Tasks.new do |gem|
16
+ gem.name = project_name
17
+ gem.summary = "Run any kind of code in parallel processes"
18
+ gem.email = "grosser.michael@gmail.com"
19
+ gem.homepage = "http://github.com/grosser/#{project_name}"
20
+ gem.authors = ["Michael Grosser"]
21
+ gem.rubyforge_project = 'parallel'
22
+ end
23
+ Jeweler::RubyforgeTasks.new
24
+ rescue LoadError
25
+ puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
26
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.3.1
@@ -0,0 +1,113 @@
1
+ class Parallel
2
+ VERSION = File.read( File.join(File.dirname(__FILE__),'..','VERSION') ).strip
3
+
4
+ def self.in_threads(count = 2)
5
+ out = []
6
+ threads = []
7
+
8
+ count.times do |i|
9
+ threads[i] = Thread.new do
10
+ out[i] = yield(i)
11
+ end
12
+ end
13
+
14
+ threads.each{|t| t.join }
15
+ out
16
+ end
17
+
18
+ def self.in_processes(count = processor_count)
19
+ # Start writing results into n pipes
20
+ reads = []
21
+ writes = []
22
+ pids = []
23
+ count.times do |i|
24
+ reads[i], writes[i] = IO.pipe
25
+ pids << Process.fork do
26
+ Marshal.dump(yield(i), writes[i]) # Serialize result
27
+ end
28
+ end
29
+
30
+ kill_on_ctrl_c(pids)
31
+
32
+ # Collect results from pipes simultanously
33
+ # otherwise pipes get stuck when to much is written (buffer full)
34
+ out = []
35
+ collectors = []
36
+ count.times do |i|
37
+ collectors << Thread.new do
38
+ writes[i].close
39
+
40
+ out[i] = ''
41
+ while text = reads[i].gets
42
+ out[i] += text
43
+ end
44
+
45
+ reads[i].close
46
+ end
47
+ end
48
+
49
+ collectors.each{|c| c.join }
50
+
51
+ out.map{|x| Marshal.load(x) } # Deserialize results
52
+ end
53
+
54
+ def self.map(array, options = {})
55
+ require 'thread' # to get Thread.exclusive
56
+
57
+ if options[:in_threads]
58
+ method = :in_threads
59
+ size = options[method]
60
+ else
61
+ method = :in_processes
62
+ size = options[method] || processor_count
63
+ end
64
+
65
+ # work in #{size} threads that use threads/processes
66
+ results = []
67
+ current = -1
68
+
69
+ in_threads(size) do
70
+ # as long as there are more items, work on one of them
71
+ loop do
72
+ index = Thread.exclusive{ current+=1 }
73
+ break if index >= array.size
74
+ results[index] = *send(method, 1){ yield array[index] }
75
+ end
76
+ end
77
+
78
+ results
79
+ end
80
+
81
+ def self.processor_count
82
+ case RUBY_PLATFORM
83
+ when /darwin/
84
+ `hwprefs cpu_count`.to_i
85
+ when /linux/
86
+ `cat /proc/cpuinfo | grep processor | wc -l`.to_i
87
+ end
88
+ end
89
+
90
+ private
91
+
92
+ def self.in_groups_of(array, size)
93
+ results = []
94
+ loop do
95
+ slice = array[(results.size * size)...((results.size+1) * size)]
96
+ if slice.nil? or slice.empty?
97
+ break
98
+ else
99
+ results << slice
100
+ end
101
+ end
102
+ results
103
+ end
104
+
105
+ #handle user interrup (Ctrl+c)
106
+ def self.kill_on_ctrl_c(pids)
107
+ Signal.trap :SIGINT do
108
+ $stderr.puts 'Parallel execution interrupted, exiting ...'
109
+ pids.each { |pid| Process.kill(:KILL, pid) }
110
+ exit 1 # Quit with 'failed' signal
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,64 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{parallel}
8
+ s.version = "0.3.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Michael Grosser"]
12
+ s.date = %q{2009-10-04}
13
+ s.email = %q{grosser.michael@gmail.com}
14
+ s.extra_rdoc_files = [
15
+ "README.markdown"
16
+ ]
17
+ s.files = [
18
+ "README.markdown",
19
+ "Rakefile",
20
+ "VERSION",
21
+ "lib/parallel.rb",
22
+ "parallel.gemspec",
23
+ "spec/cases/parallel_influence_outside_data.rb",
24
+ "spec/cases/parallel_map.rb",
25
+ "spec/cases/parallel_map_sleeping.rb",
26
+ "spec/cases/parallel_map_uneven.rb",
27
+ "spec/cases/parallel_raise.rb",
28
+ "spec/cases/parallel_sleeping_2.rb",
29
+ "spec/cases/parallel_start_and_kill.rb",
30
+ "spec/cases/parallel_with_detected_cpus.rb",
31
+ "spec/cases/parallel_with_set_processes.rb",
32
+ "spec/parallel_spec.rb",
33
+ "spec/spec_helper.rb"
34
+ ]
35
+ s.homepage = %q{http://github.com/grosser/parallel}
36
+ s.rdoc_options = ["--charset=UTF-8"]
37
+ s.require_paths = ["lib"]
38
+ s.rubyforge_project = %q{parallel}
39
+ s.rubygems_version = %q{1.3.5}
40
+ s.summary = %q{Run any kind of code in parallel processes}
41
+ s.test_files = [
42
+ "spec/parallel_spec.rb",
43
+ "spec/spec_helper.rb",
44
+ "spec/cases/parallel_raise.rb",
45
+ "spec/cases/parallel_sleeping_2.rb",
46
+ "spec/cases/parallel_start_and_kill.rb",
47
+ "spec/cases/parallel_with_set_processes.rb",
48
+ "spec/cases/parallel_influence_outside_data.rb",
49
+ "spec/cases/parallel_map_sleeping.rb",
50
+ "spec/cases/parallel_map_uneven.rb",
51
+ "spec/cases/parallel_with_detected_cpus.rb",
52
+ "spec/cases/parallel_map.rb"
53
+ ]
54
+
55
+ if s.respond_to? :specification_version then
56
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
57
+ s.specification_version = 3
58
+
59
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
60
+ else
61
+ end
62
+ else
63
+ end
64
+ end
@@ -0,0 +1,8 @@
1
+ require 'spec/spec_helper.rb'
2
+
3
+ x = 'yes'
4
+
5
+ Parallel.in_processes(2) do
6
+ x = 'no'
7
+ end
8
+ print x
@@ -0,0 +1,6 @@
1
+ require 'spec/spec_helper.rb'
2
+
3
+ result = Parallel.map(['a','b','c','d']) do |x|
4
+ "-#{x}-"
5
+ end
6
+ print result * ' '
@@ -0,0 +1,5 @@
1
+ require 'spec/spec_helper.rb'
2
+
3
+ Parallel.map(['a','b','c','d']) do |x|
4
+ sleep 1
5
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec/spec_helper.rb'
2
+
3
+ Parallel.map([1,2,1,2]) do |x|
4
+ sleep 2 if x == 1
5
+ end
@@ -0,0 +1,10 @@
1
+ require 'spec/spec_helper.rb'
2
+
3
+ begin
4
+ Parallel.in_processes(2) do
5
+ raise "TEST"
6
+ end
7
+ puts "FAIL"
8
+ rescue RuntimeError
9
+ puts $!.message
10
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec/spec_helper.rb'
2
+
3
+ Parallel.in_processes(5) do
4
+ sleep 2
5
+ end
@@ -0,0 +1,6 @@
1
+ require 'spec/spec_helper.rb'
2
+
3
+ Parallel.in_processes(2) do
4
+ sleep 10
5
+ puts "I should have been killed earlier..."
6
+ end
@@ -0,0 +1,6 @@
1
+ require 'spec/spec_helper.rb'
2
+
3
+ x = Parallel.in_processes do
4
+ "HELLO"
5
+ end
6
+ puts x
@@ -0,0 +1,6 @@
1
+ require 'spec/spec_helper.rb'
2
+
3
+ x = Parallel.in_processes(5) do
4
+ "HELLO"
5
+ end
6
+ puts x
@@ -0,0 +1,110 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe Parallel do
4
+ describe :in_processes do
5
+ before do
6
+ @cpus = Parallel.processor_count
7
+ end
8
+
9
+ it "executes with detected cpus" do
10
+ `ruby spec/cases/parallel_with_detected_cpus.rb`.should == "HELLO\n" * @cpus
11
+ end
12
+
13
+ it "set ammount of parallel processes" do
14
+ `ruby spec/cases/parallel_with_set_processes.rb`.should == "HELLO\n" * 5
15
+ end
16
+
17
+ it "does not influence outside data" do
18
+ `ruby spec/cases/parallel_influence_outside_data.rb`.should == "yes"
19
+ end
20
+
21
+ it "kills the processes when the main process gets killed through ctrl+c" do
22
+ t = Time.now
23
+ lambda{
24
+ Thread.new do
25
+ `ruby spec/cases/parallel_start_and_kill.rb`
26
+ end
27
+ sleep 1
28
+ running_processes = `ps -f`.split("\n").map{|line| line.split(/\s+/)}
29
+ parent = running_processes.detect{|line| line.include?("00:00:00") and line.include?("ruby") }[1]
30
+ `kill -2 #{parent}` #simulates Ctrl+c
31
+ }.should_not change{`ps`.split("\n").size}
32
+ Time.now.should be_close(t, 3)
33
+ end
34
+
35
+ it "saves time" do
36
+ t = Time.now
37
+ `ruby spec/cases/parallel_sleeping_2.rb`
38
+ Time.now.should be_close(t, 3)
39
+ end
40
+
41
+ it "raises when one of the processes raises" do
42
+ pending 'there is some kind of error, but not the original...'
43
+ `ruby spec/cases/parallel_raise.rb`.should == 'TEST'
44
+ end
45
+ end
46
+
47
+ describe :in_threads do
48
+ it "saves time" do
49
+ t = Time.now
50
+ Parallel.in_threads(3){ sleep 2 }
51
+ Time.now.should be_close(t, 3)
52
+ end
53
+
54
+ it "does not create new processes" do
55
+ lambda{ Thread.new{ Parallel.in_threads(2){sleep 1} } }.should_not change{`ps`.split("\n").size}
56
+ end
57
+
58
+ it "returns results as array" do
59
+ Parallel.in_threads(4){|i| "XXX#{i}"}.should == ["XXX0",'XXX1','XXX2','XXX3']
60
+ end
61
+
62
+ it "raises when a thread raises" do
63
+ lambda{ Parallel.in_threads(2){|i| raise "TEST"} }.should raise_error("TEST")
64
+ end
65
+ end
66
+
67
+ describe :map do
68
+ it "saves time" do
69
+ t = Time.now
70
+ `ruby spec/cases/parallel_map_sleeping.rb`
71
+ Time.now.should be_close(t, 3)
72
+ end
73
+
74
+ it "executes with given parameters" do
75
+ `ruby spec/cases/parallel_map.rb`.should == "-a- -b- -c- -d-"
76
+ end
77
+
78
+ it "starts new process imediatly when old exists" do
79
+ t = Time.now
80
+ `ruby spec/cases/parallel_map_uneven.rb`
81
+ Time.now.should be_close(t, 3)
82
+ end
83
+
84
+ it "does not flatten results" do
85
+ Parallel.map([1,2,3], :in_threads=>2){|x| [x,x]}.should == [[1,1],[2,2],[3,3]]
86
+ end
87
+
88
+ it "can run in threads" do
89
+ Parallel.map([1,2,3,4,5,6,7,8,9], :in_threads=>4){|x| x+2 }.should == [3,4,5,6,7,8,9,10,11]
90
+ end
91
+ end
92
+
93
+ describe :in_groups_of do
94
+ it "works for empty" do
95
+ Parallel.send(:in_groups_of, [], 3).should == []
96
+ end
97
+
98
+ it "works for smaller then count" do
99
+ Parallel.send(:in_groups_of, [1,2], 3).should == [[1,2]]
100
+ end
101
+
102
+ it "works for count" do
103
+ Parallel.send(:in_groups_of, [1,2,3], 3).should == [[1,2,3]]
104
+ end
105
+
106
+ it "works for larger than count" do
107
+ Parallel.send(:in_groups_of, [1,2,3,4], 3).should == [[1,2,3],[4]]
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,4 @@
1
+ # ---- requirements
2
+ $LOAD_PATH << File.expand_path("../lib", File.dirname(__FILE__))
3
+
4
+ require 'parallel'
metadata ADDED
@@ -0,0 +1,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: parallel
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.1
5
+ platform: ruby
6
+ authors:
7
+ - Michael Grosser
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-10-04 00:00:00 +02:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: grosser.michael@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.markdown
24
+ files:
25
+ - README.markdown
26
+ - Rakefile
27
+ - VERSION
28
+ - lib/parallel.rb
29
+ - parallel.gemspec
30
+ - spec/cases/parallel_influence_outside_data.rb
31
+ - spec/cases/parallel_map.rb
32
+ - spec/cases/parallel_map_sleeping.rb
33
+ - spec/cases/parallel_map_uneven.rb
34
+ - spec/cases/parallel_raise.rb
35
+ - spec/cases/parallel_sleeping_2.rb
36
+ - spec/cases/parallel_start_and_kill.rb
37
+ - spec/cases/parallel_with_detected_cpus.rb
38
+ - spec/cases/parallel_with_set_processes.rb
39
+ - spec/parallel_spec.rb
40
+ - spec/spec_helper.rb
41
+ has_rdoc: true
42
+ homepage: http://github.com/grosser/parallel
43
+ licenses: []
44
+
45
+ post_install_message:
46
+ rdoc_options:
47
+ - --charset=UTF-8
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: "0"
55
+ version:
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: "0"
61
+ version:
62
+ requirements: []
63
+
64
+ rubyforge_project: parallel
65
+ rubygems_version: 1.3.5
66
+ signing_key:
67
+ specification_version: 3
68
+ summary: Run any kind of code in parallel processes
69
+ test_files:
70
+ - spec/parallel_spec.rb
71
+ - spec/spec_helper.rb
72
+ - spec/cases/parallel_raise.rb
73
+ - spec/cases/parallel_sleeping_2.rb
74
+ - spec/cases/parallel_start_and_kill.rb
75
+ - spec/cases/parallel_with_set_processes.rb
76
+ - spec/cases/parallel_influence_outside_data.rb
77
+ - spec/cases/parallel_map_sleeping.rb
78
+ - spec/cases/parallel_map_uneven.rb
79
+ - spec/cases/parallel_with_detected_cpus.rb
80
+ - spec/cases/parallel_map.rb