6. diminishing returns
• communication takes finite time
• so doubling processors never doubles a
system’s realworld performance
• realtime capacity ~70% theoretical capacity
• or less!!!
• independence = performance
Friday 12 November 2010
7. “... for the first time in history, no one is building a
much faster sequential processor. If you want your
programs to run significantly faster (...) you’re going
to have to parallelize your program.”
Hennessy and Patterson “Computer Architectures” (4th
edition, 2007)
Friday 12 November 2010
9. an aide to good design
• decouples independent tasks
• encourages data to flow efficiently
• supports parallel execution
• enhances scalability
• improves program comprehensibility
Friday 12 November 2010
16. icon
• a procedural language
• with a single thread of execution
• generators are decoupled coexpressions
• and goal-directed evaluation
• creates flexible flow-of-control
Friday 12 November 2010
17. icon coexpressions
procedure main()
n := create(seq(1)10)
s := create(squares())
c := create(cubes())
while write(@n,“t”, @s,“t”, @c)
end
procedure seq(start)
repeat {
suspend start
start += 1
}
end
procedure squares()
odds := 3
sum := 1
repeat {
suspend sum
sum +:= odds
odds +:= 2
}
end
procedure cubes()
odds := 1
sum := 1
i := create(seq(2))
repeat {
suspend sum
sum +:= 1 + (odds * 6)
odds +:= @i
}
end
1 1 1
2 4 8
3 9 27
4 16 64
5 25 125
6 36 216
7 49 343
8 64 512
9 81 729
10 100 1000
Friday 12 November 2010
18. ruby fibers
• coexpressions
• bound to a single thread
• scheduled cooperatively
• the basis for enumerators
• library support for full coroutines
Friday 12 November 2010
19. ruby coexpressions
def seq start = 1
Fiber.new do
loop do
Fiber.yield start
start += 1
end
end
end
n = seq()
s = Fiber.new do
sum, odds = 1, 3
loop do
Fiber.yield sum
sum += odds
odds += 2
end
end
c = Fiber.new do
sum, odds, i = 1, 1, seq(2)
loop do
Fiber.yield sum
sum += 1 + (odds * 6)
odds += i.resume
end
end
10.times do
puts “#{n.resume}t#{s.resume}t#{c.resume}”
end
1 1 1
2 4 8
3 9 27
4 16 64
5 25 125
6 36 216
7 49 343
8 64 512
9 81 729
10 100 1000
Friday 12 November 2010
20. ruby coroutines
require 'fiber'
def login
Fiber.new do |server, name, password|
puts "#{server.transfer(Fiber.current)} #{name}"
puts "#{server.transfer name} #{password}"
puts “login #{server.transfer password}”
end
end
def authenticate
Fiber.new do |client|
name = client.transfer "name:"
password = client.transfer "password:"
if password == "ultrasecret" then
client.transfer "succeeded"
else
client.transfer "failed"
end
end
end
login.transfer authenticate, "jane doe", "ultrasecret"
login.transfer authenticate, "john doe", "notsosecret"
name: jane doe
password: ultrasecret
login succeeded
name: john doe
password: notsosecret
login failed
Friday 12 November 2010
21. icon revisited
procedure powers()
repeat {
while e := get(queue) do
write(e,“t”, e^ 2,“t”, e^ 3)
e @&source
}
end
procedure process(L)
consumer := get(L)
every producer := !L do
while put(queue, @producer) do
if *queue > 3 then @consumer
@consumer
end
global queue
procedure main()
queue := []
process{ powers(), 1 to 5, seq(6, 1)5 }
end
1 1 1
2 4 8
3 9 27
4 16 64
5 25 125
6 36 216
7 49 343
8 64 512
9 81 729
10 100 1000
Friday 12 November 2010
22. a ruby equivalent
require 'fiber'
def process consumer, *fibers
q = consumer.transfer(Fiber.current)
fibers.each do |fiber|
while fiber.alive?
q.push(fiber.resume)
q = consumer.transfer(q) if q.length
> 3
end
end
consumer.transfer q
end
powers = Fiber.new do |caller|
loop do
caller.transfer([]).each do |e|
puts "#{e}t#{e ** 2}t#{e ** 3}" rescue nil
end
end
end
low_seq = Fiber.new do
5.times { |i| Fiber.yield i + 1 }
nil
end
high_seq = Fiber.new do
(6..10).each { |i| Fiber.yield i }
end
process powers, low_seq, high_seq
1 1 1
2 4 8
3 9 27
4 16 64
5 25 125
6 36 216
7 49 343
8 64 512
9 81 729
10 100 1000
Friday 12 November 2010
25. processes + threads
Process 2
RAMmemory space
Process 1
thread1
scheduler (OS)
CPU CPU
memory space
thread2 t1 t2 t3
Friday 12 November 2010
26. cooperative preemptive
active task has full control scheduler controls task activity
runs until it yields control switches tasks automatically
tasks transfer control directly a task can still yield control
within one thread several threads
schedulers
Friday 12 November 2010
28. process thread
address space
kernel resources
scheduling
communication
control
private shared
private + shared shared
kernel varies
IPC via kernel in process
children in process
feature comparison
Friday 12 November 2010
29. process creation
• unix spawns
• windows cuts from whole cloth
• ruby wraps this many ways
• but we’re mostly interested in fork
Friday 12 November 2010
30. fork
• creates a new process
• in the same state as its parent
• but executing a different code path
• forking is complicated by pthreads
• and ruby’s default garbage collector
Friday 12 November 2010
31. pipes
• creates an I/O channel between processes
• unnamed pipes join two related processes
• posix named pipes
• live in the file system
• have user permissions
• persist independently of processes
Friday 12 November 2010
32. def execute &block
child_input, parent_input = IO.pipe
pid = fork do
child_input.close
result = block.call
parent_input.write result.to_json
parent_input.close
end
parent_input.close
sorted = JSON.parse child_input.read
child_input.close
Process.waitpid pid
return sorted
end
forking
Friday 12 November 2010
33. context switching
Operating System Benchmark Operation Time (ms)
LinuxLinuxLinuxLinuxLinux
Windows NTWindows NTWindows NTWindows NTWindows NT
spawn new process fork() / exec() 6,000
clone current process fork() 1,000
spawn new thread pthread_create 0,300
switch current process sched_yield() 0,019
switch current thread sched_yield() 0,019
spawn new process spawnl() 12,000
clone current process N/A ---
spawn new thread pthread_create() 0,900
switch current process Sleep(0) 0,010
switch current thread Sleep(0) 0,006
C Benchmarks by Gregory Travis on a P200 MMX
http://cs.nmu.edu/~randy/Research/Papers/Scheduler/
C Benchmarks by Gregory Travis on a P200 MMX
http://cs.nmu.edu/~randy/Research/Papers/Scheduler/
C Benchmarks by Gregory Travis on a P200 MMX
http://cs.nmu.edu/~randy/Research/Papers/Scheduler/
C Benchmarks by Gregory Travis on a P200 MMX
http://cs.nmu.edu/~randy/Research/Papers/Scheduler/
Friday 12 November 2010
35. semaphores
• exist independently of processes
• provide blocking access
• allowing processes to be synchronised
• nodes in the file system
• usable from Ruby with syscall
Friday 12 November 2010
36. synchronising processes
require ‘dl’
require ‘fcntl’
libc = DL::dlopen ‘libc.dylib’
open = libc[‘sem_open’,‘ISII’]
try_wait = libc[‘sem_trywait’,‘II’]
wait = libc[‘sem_wait’,‘II’]
post = libc[‘sem_post’,‘II’]
close = libc[‘sem_close’,‘II’]
process 1
s = open.call(“/tmp/s”, Fcntl::O_CREAT, 1911)[0]
wait.call s
puts “locked at #{Time.now}”
sleep 50
puts “posted at #{Time.now}”
post.call s
close.call s
process 2
s = open.call(“/tmp/s”)
t = Time.now
if try_wait.call(s)[0] == 0 then
puts “locked at #{t}”
else
puts “busy at #{t}”
wait.call s
puts “waited #{Time.now - t} seconds”
end
locked at Thu May 28 01:03:23 +0100 2009 busy at Thu May 28 01:03:36 +0100 2009
posted at Thu May 28 01:04:13 +0100 2009 waited 47.056508 seconds
Friday 12 November 2010
37. complexity
• file locking
• shared memory
• message queues
• transactional data stores
Friday 12 November 2010
39. threads under the hood
from http://www.igvita.com/2008/11/13/concurrency-is-a-myth-in-ruby/ @igrigorik
Friday 12 November 2010
40. the global lock
• compatibility for 1.8 C extensions
• only one thread executes at a time
• scheduled fairly with a timer thread
• 10 μs for Linux
• 10 ms for Windows
Friday 12 November 2010
42. threads + sockets
require 'socket'
require 'thread'
class ThreadGroup
def all_but_me &block
list. delete_if { |t|
t.name == Thread.current.name
}.each { |t| yield t }
end
def select *names, &block
list.delete_if { |t|
!names.include? t.name
}.each { |t| yield t }
end
end
class Thread
def name= n
raise ArgumentError if key? :name
self[:name] = n
self[:Q] = Queue.new
end
def send message
self[:Q].enq message
end
def bind_transmitter socket
raise ArgumentError if key? :XMIT
self[:socket] = socket
self[:XMIT] = Thread.new(socket, self[:Q]) do |s, q|
loop do
case message = q.deq
when :EXIT: Thread.current.exit
else s.puts message
end
Thread.pass
end
end
end
def disconnect
send "Goodbye #{name}"
send :EXIT
self[:XMIT].join
self[:socket].close
exit
end
end
Friday 12 November 2010
43. threads + sockets
class ChatServer < TCPServer
def initialize port
@receivers = ThreadGroup.new
@transmitters = ThreadGroup.new
@deceased = ThreadGroup.new
super
end
def register name, socket
@receivers.add(t = Thread.current)
t.name = name
@transmitters.add t.bind_transmitter(socket)
broadcast "#{name} has joined the conversation"
t.send "Welcome #{name}"
end
def connect socket
loop do
begin
socket.print "Please enter your name: "
register socket.readline.chomp).downcase, socket
break
rescue ArgumentError
socket.puts "That name is already in use"
end
end
end
def send message, name = Thread.current.name
@receivers.select(name) { |t| t.send message }
end
def broadcast message
@receivers.all_but_me { |t| t.send message }
end
def listen socket
t = Thread.current
loop do
message = socket.readline.chomp
case message.downcase
when "bye" raise EOFError
when "known" list_known_users
when "quit" raise SystemExit
else broadcast "#{t.name}: #{message}"
end
end
end
def run
while socket = accept
Thread.new(socket) do |socket|
begin
connect socket
listen socket
rescue SystemExit
broadcast "#{Thread.current.name} terminated this conversation"
broadcast :EXIT
send :EXIT
@receivers.all_but_me { |t| t.transmitter.join }
Kernel.exit 0
rescue EOFError
disconnect
end
end
end
end
end
ChatServer.new(3939).run
Friday 12 November 2010
44. the macruby twist
• grand central dispatch
• uses an optimal number of threads
• state is shared but not mutable
• object-level queues for atomic mutability
Friday 12 November 2010
45. clojure
lisp dialect for the JVMlisp dialect for the JVM
refs software transactional memory
agents independent, asynchronous change
vars in-thread mutability
check outTim Bray’s Concur.next seriescheck outTim Bray’s Concur.next series
Friday 12 November 2010
47. ruby can do that too
require 'clojure'
include Clojure
def parallel_transfer(amount)
Ref.dosync do
@balance.alter {|b| b + amount }
end
end
def parallel_stm(amount, nthreads)
threads = []
10.times do
threads << Thread.new do
parallel_transfer(amount)
end
end
threads.each {|t| t.join }
@balance.deref
end
@balance = Ref.new(0)
puts parallel_stm(10,10)
Friday 12 November 2010
49. vector processing
• collections are first-class values
• single instruction multiple data
• each datum is processed independently
• successive instructions can be pipelined
• so long as there are no side-effects
Friday 12 November 2010
50. map/reduce
• decompose into independent elements
• process each element separately
• use functional code without side-effects
• recombine the elements
• intrinsically suited to parallel execution
Friday 12 November 2010
51. a two-phase operation
concurrent sequential
(0..5).
to_a.
each { |i| puts i }
x = 0
(0..5).
to_a.
each { |i| x = x + i }
(0..5).
to_a.
map { |i| i ** 2 }
(0..5).
to_a.
inject { |sum, i| sum + i }
Friday 12 November 2010
52. parallel
require 'brute_force'
require 'parallel'
# can be run with :in_processes as well
mapped = Parallel.map((0..3).to_a, :in_threads => 4) do |num|
map("english.#{num}") # hash the whole dictionary
end
hashed = "71aa27d3bf313edf99f4302a65e4c042"
puts reduce(hashed, mapped) # returns “zoned”
Friday 12 November 2010
53. algebra, actors + events
synchronising concurrency via communication
Friday 12 November 2010
54. process calculi
• mathematical model of interaction
• processes and events
• (a)synchronous message passing
• named channels with atomic semantics
Friday 12 November 2010
55. go
• statically-typed compiled systems language
• class-free object-orientation
• garbage collection
• independent lightweight coroutines
• implicit cross-thread scheduling
• channels are a first-class datatype
Friday 12 November 2010
56. package main
import "syscall"
func (c *Clock) Start() {
if !c.active {
go func() {
c.active = true
for i := int64(0); ; i++ {
select {
case status := <- c.Control:
c.active = status
default:
if c.active {
c.Count <- i
}
syscall.Sleep(c.Period)
}
}
}()
}
}
type Clock struct {
Period int64
Count chan int64
Control chan bool
active bool
}
func main() {
c := Clock{1000, make(chan int64), make(chan bool), false}
c.Start()
for i := 0; i < 3; i++ {
println("pulse value", <-c.Count, "from clock")
}
println("disabling clock")
c.Control <- false
syscall.Sleep(1000000)
println("restarting clock")
c.Control <- true
println("pulse value", <-c.Count, "from clock")
}
produces:
pulse value 0 from clock
pulse value 1 from clock
pulse value 2 from clock
disabling clock
restarting clock
pulse value 106 from clock
a signal generator
Friday 12 November 2010
57. homework
• write a signal generator in ruby
• hints:
• it’s very easy
• look at the thread + socket example
• use atomic queues
• and yes, ruby grok’s csp
Friday 12 November 2010
58. actor model
•named actors
•fully independent of each other
•asynchronous message passing
•and no shared state
Friday 12 November 2010
62. rubinius actors
• implemented as ruby threads
• each thread has an inbox
• cross-VM communication
Friday 12 November 2010
63. rubinius actors
require 'quick_sort'
require 'actor'
class RbxActorSort
" def execute(&block)
" " current = Actor.current
" " Actor.spawn(current) {|current| current.send
(block.call) }
" " Actor.receive # could have filter
" end
end
puts q = QuickSort.new([1,7,3,2,77,23,4,2,90,100,33,2,4],
RbxActorSort).sort
Friday 12 November 2010
64. ruby revactor
• erlang-like semantics
• actor spawn/receive
• filter
• uses fibers for cooperative scheduling
• so only works on ruby 1.9
• non-blocking network access in ruby 1.9.2
Friday 12 November 2010
65. promises + futures
• abstraction for delayed execution
• a promise is calculated in parallel
• a future is calculated on demand
• caller will block when requesting result
• until that result is ready
Friday 12 November 2010
66. ruby futures
Lazy.rb gem (@mentalguy)
require 'lazy'
require 'lazy/futures'
def fib(n)
return n if (0..1).include? n
fib(n-1) + fib(n-2) if n > 1
end
puts "before first future"
future1 = Lazy::Future.new { fib(40) }
puts "before second future"
future2 = Lazy::Future.new { fib(40) }
puts "and now we're waiting for results ... getting futures
fulfilled is blocking"
puts future1
puts future2
Friday 12 November 2010
67. we didn’t cover
• tuple spaces (Rinda)
• event-driven IO
• petri nets
• ...
Friday 12 November 2010