100% found this document useful (2 votes)
3K views67 pages

Fast, Simple I/O Concurrency For Ruby Ruby のための、高速・簡単な並行 Io: Rubykaigi2009

Download as pdf or txt
Download as pdf or txt
Download as pdf or txt
You are on page 1/ 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

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy