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 35 end

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 1000 500 0 ~200 Bytes ~1 KB ~30KB ~126KB

Mongrel Thin

File Size

45

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

MB/sec

150 100 50 0

Mongrel Thin

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 100 50 0 10/40 20/40 40/40

Mongrel Thin NB Thin

Concurrency / Requests

50

What about small files?

51

Thin vs Mongrel (small static files)
4000 3500

Requests / Sec

3000 2500 2000 1500 1000 500 0 ~200 Bytes ~1 KB ~30KB ~126KB

Mongrel Thin Thin NB

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

Requests / Sec

10000 8000 6000 4000 2000 0 1000/10000

Blocking Forking Threaded Reactor NeverBlock

Concurrency / Requests

58

Servers with 10ms delay
12000

Requests / Sec

10000 8000 6000 4000 2000 0 1000/10000

Blocking Forking Threaded Reactor NeverBlock

Concurrency / Requests

59

Servers with 1 second delay
450 400 350 300 250 200 150 100 50 0

Requests / Sec

Blocking Forking Threaded Reactor NeverBlock

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 同期フリーなマルチスレッド automatic fiber scheduling 自動的な fiber スケジューリング high I/O performance ハイ I/O パフォーマンス
62

NeverBlock enables

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