Download - プログラミング言語のパラダイムシフトーScalaから見る関数型と並列性時代の幕開けー
プログラミング言語のパラダイムシフト
Scalaから見る関数型と並列性時代の幕開け
安田裕介
concurrency なぜ並列性なのか
ムーアの法則
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://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
• 非同期計算の結果のための統一インターフェースとしてのコレクション
• 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コンプリヘンション
並列に実行並列に実行
• 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とは
• 並列実行単位となるオブジェクト
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