プログラミング言語のパラダイムシフトーscalaから見る関数型と並列性時代の幕開けー...

44
プログラミング言語の パラダイムシフト Scalaから見る関数型と並列性時代の幕開け 安田裕介

Upload: tanukkii

Post on 21-Apr-2017

7.032 views

Category:

Engineering


4 download

TRANSCRIPT

プログラミング言語のパラダイムシフト

Scalaから見る関数型と並列性時代の幕開け

安田裕介

concurrency なぜ並列性なのか

ムーアの法則

http://spray.io/duse/#/2

トランジスタの数は1.5年ごとに倍になる

ムーアの法則

http://spray.io/duse/#/2

単一スレッドの パフォーマンスは 限界に達した

今後は コアが増える形で トランジスタの集積率を 増やす形になる

トランジスタの数は1.5年ごとに倍になる

アムダールの法則

http://spray.io/duse/#/3

並列計算の分野において、複数のプロセッサを使ったときの 理論上の性能向上の限界を予測する法則

アムダールの法則

98.7%

75%

99.2%80%の性能を出すのに

必要な並列度

96.4%

http://spray.io/duse/#/3

並列計算の分野において、複数のプロセッサを使ったときの 理論上の性能向上の限界を予測する法則

アムダールの法則

並列性の低い プログラムは コアが増えても まったく速くならず、 リソースを無駄にする

並列性の高い プログラムは コアが増えるほど 速くなる

http://spray.io/duse/#/3

並列計算の分野において、複数のプロセッサを使ったときの 理論上の性能向上の限界を予測する法則

並列性の低いアプリケーションは CPUを使い切れない

PHP + Apache CPU: 32コア Intel(R) Xeon(R) CPU E5-2650 0 @ 2.00GHz

ハードウェアの進化を 待っていれば

プログラムが速くなる 時代は終わった

時代は 並列分散プログラミングへ

並列分散処理言語• Scala

• Erlang

• Haskell

• Elixir

• Rust

• (Go)

• (Pony) いずれもが関数型の性質をもつ

並列分散処理の技術

http://spray.io/duse/#/5

スレッド

http://spray.io/duse/#/5

スレッドは恐ろしい

http://docs.scala-lang.org/ja/overviews/core/futures.html

問題: “na”*16と”BATMAN”の

aとAの合計totalAはいくつ?

スレッドは恐ろしい

http://docs.scala-lang.org/ja/overviews/core/futures.html

問題: “na”*16と”BATMAN”の

aとAの合計totalAはいくつ?

A: 2 か 16 か 18

読み込みと書き込みに 原子性がないことと、

実行順序が不明なことが原因

スレッドの問題• 原子性がない

• 順番の保証がない

• デッドロック、レースコンディション

• リソースとして貴重

• 排他制御、待ち合わせなど、コードが複雑になる

• デバッグが困難

Future

http://spray.io/duse/#/5

Future

• 非同期計算の結果のための統一インターフェースとしてのコレクション

• Scalaにおける並列実行の単位

• map, flatMapなどのモナドコンビネーターメソッド搭載

"Future" must { import scala.concurrent.ExecutionContext.Implicits.global "map and flatMap" in { val futureMessage = Future { Thread.sleep(1000); 1 }.flatMap(value => Future { Thread.sleep(1000); value + 1 }).map(s => s"This is a value of future after $s seconds") Await.result(futureMessage, 5 seconds) must be("This is a value of future after 2 seconds") } "for comprehension" in { val futureMessage = for { s1 <- Future { Thread.sleep(1000); 1 } s2 <- Future { Thread.sleep(1000); s1 + 1 } } yield s"This is a value of future after $s2 seconds" Await.result(futureMessage, 5 seconds) must be("This is a value of future after 2 seconds") }}

Future

並列に実行

並列に実行 並列に実行

forコンプリヘンション

並列に実行並列に実行

Parallel Collection

http://spray.io/duse/#/5

• Scalaコレクションの並列版

• Scalaコレクションと同じAPIだが、内部実行は並列

• parメソッドによりScalaコレクションから並列コレクションに透過的に変換できる

• Scalaコレクションと使いかたが同じなので簡単に使える

Parallel Collection

Parallel Collection

"parallel collection" must { "behave same as standard one" in { val list = (0 to 9999).toList list.map(_ + 1).filter(_ % 2 == 0).fold(0)(_ + _) must be list.par.map(_ + 1).filter(_ % 2 == 0).fold(0)(_ + _) }}

通常のコレクション

並列コレクション

使い方まったく同じ

Actor

http://spray.io/duse/#/5

Actorとは

• 並列実行単位となるオブジェクト

Future: 関数 Actor: オブジェクト並列実行単位

Actorは速い

http://spray.io/blog/2013-05-24-benchmarking-spray/

ActorベースのフレームワークSprayはJVM史上最速

Actorは効率的

• 4096スレッドで1Gbのメモリーを消費(linux x64)

• 2700000アクターで1Gbのメモリーを消費

Akka in Actionより

Actorは堅牢

• Erlangで作られたAXD301スイッチは99.9999999% 可用性を実現

Reactive Manifesto

http://www.reactivemanifesto.org/

• 即応性 (Responsive)

• 耐障害性 (Resilient)

• 弾力性 (Elastic)

• メッセージ駆動 (Message Driven)

リアクティブなシステムを構築せよ

以降Akka actorを元に説明

Actor

Actor

Actorの機能概観1.SEND

2.CREATE

4.BECOME

3.SUPERVISE

振る舞いを変える

複製する監視する

メッセージを 送る

Actor の仕組みmailbox

thread pool

partial function

message

event loop

thread

Actorはスレッドの問題を解決する

• 原子性:アクター内部の処理は並列ではなく順次実行されるので、通常のプログラミングと同じ

• 実行順序:アクター内部の処理は並列ではなく順次実行されるので、通常のプログラミングと同じ

• カプセル化:他のスレッドからアクター内はアクセスできない

• 安全性:アクターはブロックしないので、デッドロックはおきない

• リソース効率性:スレッドプールから論理スレッドが確保できたときだけ、メッセージが処理される

Actorによる並列プログラミングclass CountAActor extends Actor { var totalA = 0 def receive: Receive = { case "How many?" => sender ! totalA case text: String => totalA += text.toUpperCase().count(_ == 'A') }} object CountAActor { def props = Props(new CountAActor)}

class SampleTest extends TestKit(ActorSystem("SampleSystem", ConfigFactory.empty()))with WordSpecLike with MustMatchers with ImplicitSender with StopSystemAfterAll { "CountAActor" must { "count A and a" in { val countAActor = system.actorOf(CountAActor.props, "countAActor") countAActor ! "na" * 16 countAActor ! "BATMAN!" countAActor ! "How many?" expectMsg(18) } }}

非同期にメッセージを送る

必ず18になる

Actor内部は順次実行されるので、 安全に状態を更新できる

(直ちに処理されるのではなくmailboxに貯まる)

内部状態は絶対に外から見えないので、 他のスレッドから守られている

SEND: メッセージの送信• メッセージの送信は、非同期でブロックしない

• メッセージの送信先はアクター参照(ActorRef)であり、アクターインスタンスではない

• アクターインスタンスを直接取得する方法はなく、外部から完全に守られている

• アクター参照は実際の送信先を抽象化する(位置透過性)

位置透過性 Location Transparency

• アクターのインスタンスが物理的にどこに存在しようが、同じようにメッセージを送れる

doc.akka.io

• 同じプロセス • 別のプロセス • 別のネットワーク上のサーバー

CREATE: 複製• アクターは簡単に複製できる

• アクターは気軽にどんどん作ろう

• アクターはスケール簡単にスケールする

Props: アクターの設計図class CountAActor extends Actor { var totalA = 0 def receive: Receive = { case "How many?" => sender ! totalA case text: String => totalA += text.toUpperCase().count(_ == 'A') }} object CountAActor { def props = Props(new CountAActor)} class SampleTest extends TestKit(ActorSystem("SampleSystem", ConfigFactory.empty()))with WordSpecLike with MustMatchers with ImplicitSender with StopSystemAfterAll { "CountAActor" must { "count A and a in parallel" in { implicit val dispatcher = system.dispatcher implicit val timeout = Timeout(5 seconds) import akka.pattern.ask val countAActor1 = system.actorOf(CountAActor.props, "countAActor1") val countAActor2 = system.actorOf(CountAActor.props, “countAActor2") countAActor1 ! "na" * 16 countAActor2 ! “BATMAN!" val futures = Seq(countAActor1, countAActor2).map(_ ? "How many?").map(_.mapTo[Int]) val result = Future.sequence(futures).map(_.reduce(_ + _)) Await.result(result, 5 seconds) must be(18) } }}

再現可能なインスタンスを作るためのProps

Propsからアクターを2つ作成

2つのアクターにメッセージを送る

2つのアクターの計算結果を集計する

Router: 負荷分散trait RouterCreator { def createRouter = SmallestMailboxPool(100).props(CountAActor.props) } class CountARouter extends Actor with RouterCreator { val countARouter = context.actorOf(createRouter) def receive: Receive = { case hm@"How many?" => { val reducer = context.actorOf(Reducer.props(sender(), 100)) countARouter.tell(Broadcast(hm), reducer) } case msg => countARouter forward msg }} object CountARouter { def props = Props(new CountARouter)} class Reducer(sendTo: ActorRef, maxCount: Int) extends Actor { var total = 0; var count = 0 def receive: Receive = { case sum: Int => { total += sum; count += 1 if (count == maxCount) { sendTo ! total self ! PoisonPill } } }} object Reducer { def props(sendTo: ActorRef, maxCount: Int) = Props(new Reducer(sendTo, maxCount))}

100個のアクターに分散する ルーター

ルーターにメッセージを送る

最終的な結果を集計するアクター

すべてのルーティーに集計メッセージを送る

Routerclass SampleTest extends TestKit(ActorSystem("SampleSystem", ConfigFactory.empty()))with WordSpecLike with MustMatchers with ImplicitSender with StopSystemAfterAll { "Router" must { "route messages" in { val router = system.actorOf(CountARouter.props, "CountARouter") Stream.fill(10000)("BATMAN!").foreach(router ! _) router ! "How many?" expectMsg(10000 * 2) } }}

10000個のメッセージを送る集計 ルーターは100個のアクターに分散する

Remote Router 複数のサーバー上のアクターに分散する

http://doc.akka.io/docs/akka/snapshot/dev/multi-jvm-testing.html

sbt-multijvm-pluginを使ったmulti-jvmテストのデモ

位置透過性により、 送る先のアクターがどこにあってもよい

同じようにActorRefに対してメッセージを送るだけ

SUPERVISE: 監視する• 障害を起こさないように設計するのではなく、障害がおきてもいいように設計する”Let it crash”モデルを採用

• アクターは親子構造のヒエラルキーを作る

• 親が子の監視者となり、子の障害に対処する

• 階層をできるだけ深くして、障害の影響範囲を小さくする

• 状態がおかしくなってクラッシュしたアクターは再起動される(デフォルトの設定)

Let it crash! アクターの再起動親アクター

子アクター

1. 障害発生親アクター

子アクター

2. 子アクターが停止

親アクター

子アクター子アクター

親アクター

子アクター

3. 子アクターを 再生成して 入れ替え

4. 新しい子アクター インスタンスで 処理再開

(mailboxは保持)

class CrashActor extends Actor with ActorLogging { def receive: Receive = { case "Crash!!!" => throw new Exception("crashed!") } override def preStart() { log.info("preStart") } override def preRestart(reason: Throwable, message: Option[Any]) = { log.info("preRestart") super.preRestart(reason, message) } override def postRestart(reason: Throwable) { log.info("postRestart") super.postRestart(reason) } override def postStop() { log.info("postStop") }} object CrashActor { def props = Props(new CrashActor)} class Supervisor extends Actor { val crashActor = context.actorOf(CrashActor.props) def receive: Receive = { case msg => crashActor forward msg }} object Supervisor { def props = Props(new Supervisor)} class SampleTest extends TestKit(ActorSystem("SampleSystem", ConfigFactory.empty()))with WordSpecLike with MustMatchers with ImplicitSender with StopSystemAfterAll { "Supervisor" must { "crush CrashActor" in { val supervisor = system.actorOf(Supervisor.props, "supervisor") supervisor ! "Crash!!!" Thread.sleep(1000) } }

クラッシュするメッセージを 受けるアクター

CrashActorを子に持つ監視アクター

クラッシュさせる

アクターの ライフサイクルフックメソッド

再起動してる

まとめ• 今後CPUの進化はマルチコア化が駆動

• CPUを使い尽くすために、並列分散プログラミングが必要

• 並列分散プログラミングは難しい

• 抽象度・一般性のトレードオフの中で、並列処理のハードルを下げる様々な概念がある

• Future, Parallel Collection, Actor