parallel 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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