You are on page 1of 67

www.espace.com.

eg RubyKaigi2009

Fast, simple I/O concurrency for Ruby


Ruby のための、高速・簡単な並行 IO
1
Hello
こんにちは
2
Egypt's #1 Ruby shop. Embraced Ruby (and Rails) in 2006
and it currently keeps a team of 20 talented developers
busy serving clients in 4 continents

3
Mohammad Ali ( モハメド アリ )

Not the boxer!

eSpace's CTO

11 years of C, C++, Java, PHP and JavaScript

Met Ruby in 2005, it's been 4 years now

Researching software concurrency models

Backed up by a wonderful team of developers

4
What is NeverBlock?

NeverBlock is a Fiber based library that


provides I/O concurrency facilities

NeverBlock は Fiber ベースのライブラリで、


非同期イベントループの複雑さを隠しつつ I/O
並行性の性質をアプリケーションに提供する。
5
Q
How can my Ruby
program perform
I/O concurrently?
Ruby プログラムは
どうやって I/O を並
行に実行するのか?
6
A1
Concurrently ( 並行に )?
What is that?
1000.times do
socket = TCPSocket.open(host, port)
socket.write(request)
response = socket.read(chunk)
...
socket.close
end
7
Illustration
図解

8
Object 1

9
The Good
● Trivial to understand and implement
とても簡単に理解できるし、実装もできる
● No need to worry about synchronization
同期について考える必要がない

The fastest if your servers have zero latencies
もしサーバがレイテンシゼロなら最速

10
The Bad
● Introduce the slightest latency and it crawls
微少なテイテンシを持ち込み、一定幅をとり続ける
● Large latencies render it unusable
大きく遅延すると利用不能に

11
A2 Use multiple processes
マルチプロセスを使う
1000.times do
if !fork
socket = TCPSocket.open(host, port)
socket.write(request)
response = socket.read(chunk)
...
socket.close
exit
end
end
Process.waitall
12
The Good
● Easy to understand and implement
簡単に理解し、実装できる
● No synchronization needed (no shared resources)
同期処理が不要(共有リソースがない)

Scales to multiple CPUs!
マルチ CPU だとスケールする!

Scheduling ( スケジューリング ) is done by the OS
13
The Bad
● A heavy operation that can hurt if excessively used
過度に使うと実行環境にダメージを与えるほど重い処理
● Much harder if you want to share resources
リソースの共有が極めて難しい

Communication becomes a lot slower
プロセス間通信を行うと、とても遅くなる

14
www.espace.com.eg

A3 Use multiple threads


マルチスレッドを使う
@threads = []
1000.times do
@threads << Thread.new do
socket = TCPSocket.open(host, port)
socket.write(request)
response = socket.read(chunk)
...
socket.close
end
end
@threads.each{|th|th.join}
15
Illustration
図解

16
Object 6

17
The Good

Simple implementations ( 実装しよう ) are easy
● The logic inside the thread body is straight forward
スレッド本体内のロジックは一直線

Creating threads is much faster than forking (~200x)
スレッド生成はフォークに比べて極めて速い(~ 200 倍)

Scheduling is done by the OS

Communication ( 間通信 ) among threads is very fast
18
The Bad
● Synchronization for shared data is tricky
共有データの同期はトリッキーな方法が必要

Big locks cause idleness and eventually deadlocks
巨大ロックはアイドル状態を生むし、場合によってはデッドロックする
● Many Tiny locks cause degradation and races
たくさんの小さなロックは性能低下とレースの原因になる
● Ruby 1.9.x does not like to have too many threads
Ruby 1.9.x は多くのスレッドを抱えることを嫌う

19
A4 Use an event loop
イベントループを使う
(reactor = Reactor::Base.new).run do
1000.times do
socket = TCPSocket.open(host, port)
reactor.attach(:write, socket) do
socket.write(request)
reactor.detach(:write, socket)
reactor.attach(:read, socket) do
response = socket.read(chunk)
socket.close
reactor.detach(:read, socket)
end
end
end
end
20
The Good
● Extremely fast, no context switching overhead
恐ろしく速いし、コンテキストスイッチのオーバーヘッドもない
● Can scale enormously (if epoll/kqueue are used)
果てしなくスケールする ( もし epoll/kqueue が使えれば )
● Uses much less system resources than threads
スレッドと比べ、ほとんどシステムリソースを使わない

21
The Bad
● Non continuous flow, hard to understand and use
不連続なフローになるため、把握しづらく、使いにくい

If a single action blocks the whole loop is blocked
1つのアクションがブロックすれば、全体がブロックされる

Deadlocks can happen (with very careless coding)
デッドロックが起こりうる ( とてもいい加減なコーディングの場合 )

22
In Summary ( 要するに )
• Processes are easy but expensive
マルチプロセスは簡単だが高くつく
• Threads are cheaper but tricky
マルチスレッドは比較的低コストだがトリッキー
• Event loops are the best performers but too hard
イベントループはベストな性能だが難しすぎる

23
Q
What about those
fibers in Ruby
1.9.x?
Ruby 1.9.x の fiber
はどうなのだろう?
24
A Yes, Ruby 1.9.x can use fibers
(reactor = Reactor::Base.new).run do
1000.times do
Fiber.new do
fiber = Fiber.current
socket = TCPSocket.open(host, port)
reactor.attach(:write, socket){fiber.resume}
Fiber.yield
reactor.detach(:write, socket)
socket.write(request)
reactor.attach(:read, socket){fiber.resume}
Fiber.yield
reactor.detach(:read, socket)
response = socket.read(chunk)
socket.close
end.resume
end
end 25
The Good
● Very fast, switch context only when needed
とても速い。必要なときだけコンテキストを切り替える

Can scale almost as much as the event loop can do
ほぼイベントループ並にスケールすることができる
● Low memory overhead, 4KB stack space per fiber
メモリはも低い。 1 fiber あたり 4KB 程度のスタックスペース
● No race conditions in non I/O code
I/O 以外のコードでは競合状態が発生しない

26
The Bad
● Scheduling fibers is done manually
fiber のスケジューリングは手動で行う
● The event loop is still explicitly managed
イベントループは明示的に管理されている
● Recursive calls can easily run out of stack
再帰呼び出しは、簡単にスタックを使い果たす

Context switching (mem)copies the fiber stacks
コンテキストスイッチは fiber スタックをコピる
27
Q
That's it? No other
ways to do
concurrency in
Ruby?
それだけ? Ruby には
他の並行性のやり方は
ないのか? 28
A
There are many ( たくさんある )

Revactor, for example, is an Actor library implemented
using Fibers
例えば、 Revactor は Fiber で実装した Actor ライブラ
である。
● And of course, NeverBlock.
そして、勿論、 NeverBlock 。

29
NeverBlock In Action

NB::Reactor.new do
1000.times do
NB::Fiber.new do
socket = TCPSocket.open(host, port)
socket.write(request)
response = socket.read(chunk)
socket.close
end.resume
end
end
30
The Good
● Developers can write normal “blocking” Ruby code
開発者は、普通の "blocking" Ruby コードを書ける

Scheduling is handled almost transparently
スケジューリングはほぼ透過的に扱われる
● Looks a lot like threads but without synchronization
同期しない複数のスレッドのように見える
● Generally faster than processes and threads
通常、マルチスレッドや、マルチプロセスより速い

31
The Bad
● A bit slower than Event Loops
イベントループよりわずかに遅い
● Uses more memory
より多くのメモリを使う

CPU operations block
CPU 演算中は切り替わらない

32
Examples?
例は?

33
Writing to a pipe every 3 seconds
パイプに3秒ごとに書き出す
# @pipes is an array of processes to ping
NB::Reactor.run do
@pipes.each do |pipe|
NB::Fiber.run do
loop do
pipe.write('ping')
sleep 3
end
end
end
end 34
Calling slow external programs
遅い外部プログラムを呼び出す
# @images is an array of images to process
# concurrently
NB::Reactor.run do
@images.each do |image|
NB::Fiber.run do
system('slow_processor', image.path)
end
end
end 35
Network I/O with timeout
タイムアウトなしのネットワーク I/O
# connecting to a server that might not respond in time
NB::Reactor.run do
@servers.each do |s|
NB::Fiber.run do
timeout(3) do
conn = TCPSocket.connect(s.host, s.port)
conn.write(request)
response = conn.gets('\r\n\r\n')
conn.close
end
end
end
end 36 36
Large data transfers
巨大なデータの送信
# sending a large response in chunks
NB::Reactor.run do
@connections.each do |conn|
NB::Fiber.run do
response.each_chunk do |chunk|
conn.send(chunk)
end
end
end
end 37
Q
And how does
that work?
どうやって動い
ている?

38
It's all Fibers and Event loops
すべてが Fiber で、イベントループ
# the reactor supports fiber assisted
# waiting for notifications
class NB::Reactor
def wait(mode, io)
fiber = Fiber.current
self.attach(mode, io){fiber.resume}
Fiber.yield
self.detach(mode, io)
end
end 39
Along with modified I/O classes
改良された I/O クラスもある
# The I/O classes use NeverBlock for transparent
# concurrency
class MySQL
def query(sql)
send_query(sql)
# this method selects the appropriate reactor and
# calls it
NB.wait(:read, self.socket)
# the query method continues once the socket is ready
get_restult
end
end 40
All hidden from client code
クライントコードからみえない
# The query method will yield the current
# fiber till the query results are ready.
# This gives way for other fibers to run.

mysql.query(sql).each{|r|..}

# note that this requires the use of


# the MySQLPlus adapter which extends
# the original adapter with an async
# query API. 41
Illustration
図解

42
Object 7

43
Case Study:

Thin static file


serving
performance

44
Thin vs Mongrel (small static files)
3500

3000
Requests / Sec

2500

2000

1500 Mongrel
1000 Thin
500

0
~200 Bytes ~1 KB ~30KB ~126KB
File Size

45
Thin vs Mongrel (large static file, ~170MB)
300

250

200
MB/sec

150
Mongrel
100
Thin
50

0
10/40 20/40 40/40
Concurrency/Requests

46
The offending code
(lib/thin/connection.rb, line #103)

# Send the response


@response.each do |chunk|
trace { chunk }
send_data chunk
end

47
The problem?
All file data will be buffered in memory
before Eventmachine is given a
chance to run

48
The solution?
Build a new backend that uses
NeverBlock instead of EventMachine

49
Thin vs Mongrel (large static file, ~170MB)
350

300

250
MB / Sec

200

150 Mongrel
100 Thin NB
50 Thin
0
10/40 20/40 40/40
Concurrency / Requests

50
What about small
files?

51
Thin vs Mongrel (small static files)
4000
3500
Requests / Sec

3000
2500
2000
1500 Mongrel
1000 Thin
500 Thin NB
0
~200 Bytes ~1 KB ~30KB ~126KB
File Size

52
?
What exactly do I
get with
NeverBlock?
を使うことで何がで
きる?

53
Supported Concurrency Features
サポートされている並行性の特徴
● Concurrent Socket and Pipe I/O
並行 Socket & Pipe I/O
● Concurrent File I/O (by delegating to threads)
並行な File I/O ( スレッドへの委譲を使う )

Kernel#system, Kernel#sleep and Timeout.timeout

54
Other Features ( その他の特徴 )
● A Fiber pool class
Fiber プールクラス
● A generic connection pool class
汎用コネクションプールクラス

A reactor backend
reactor バックエンド

55
Supported Libraries
サポートしているライブラリ
● Net/HTTP

ActiveRecord
● ActiveResource

Thin Web Server

56
Any performance
figures or
benchmarks?
性能に関する数値や
ベンチマークは?

57
Servers with zero delay

12000

Blocking
Requests / Sec

10000

8000 Forking
Threaded
6000
Reactor
4000
NeverBlock
2000

0
1000/10000
Concurrency / Requests

58
Servers with 10ms delay

12000

Blocking
Requests / Sec

10000

8000 Forking
Threaded
6000
Reactor
4000
NeverBlock
2000

0
1000/10000
Concurrency / Requests

59
Servers with 1 second delay

450
400
Blocking
Requests / Sec

350
300 Forking
250 Threaded
200 Reactor
150
NeverBlock
100
50
0
1000/1000
Concurrency / Requests

60
Findings
● Reactor pattern provides highest performance

NeverBlock is generally the second fastest
● There is a room for more performance with faster
fiber context switching

61
Conclusion
transparent async event loops
透過的な非同期イベントループ
synchronization free threads
同期フリーなマルチスレッド
NeverBlock enables
automatic fiber scheduling
自動的な fiber スケジューリング
high I/O performance
ハイ I/O パフォーマンス

62
Wait,
there is more
もう少し聞いて。ま
だある。

63
Planned for NeverBlock
● Phusion Passenger support

Sequel Support
● AIO support for file operations

epoll/kqueue support via ktools
● Deeper integration with Ruby

64
Wish list
● Faster fiber switching

Using (set/get)context.
● Wish for context switching performance similar to
Erlang processes or Stackless Python tasklets

65
Wanted to a share a few exciting projects
undergoing at eSpace
eSpace で現在進行中のエキサイティングな
プロジェクトについて紹介したい
● Reactor, a pure Ruby event loop

Arabesque, a new Ruby queuing library
● Shihabd, the ultimate Rack server

And more

66
‫شكرا‬
Thank You
ありがとう

● http://www.espace.com.eg/neverblock
● http://github.com/oldmoe/neverblock

● http://github.com/oldmoe/mysqlplus


http://oldmoe.blogspot.com
● mohammad.ali@espace.com.eg

67

You might also like