プログラミング言語のパラダイムシフト(ダイジェスト)ーscalaから見る関数型と並列性時代の幕開けー...
TRANSCRIPT
プログラミング言語のパラダイムシフト
Scalaから見る関数型と並列性時代の幕開け
安田裕介Hackers Champloo 2015 LT
自己紹介
• 名前:安田裕介
• id: TanUkkii
• トライフォート勤務
今日話す内容
• なぜ並列分散プログラミングが重要なのか
• Scalaから見る並列分散プログラミングの現状
• このダイジェスト版 (http://www.slideshare.net/TanUkkii/functional-and-concurencyinscala)
concurrency なぜ並列性なのか
ムーアの法則
http://spray.io/duse/#/2
単一スレッドの パフォーマンスは 限界に達した
今後は コアが増える形で トランジスタの集積率を 増やす形になる
トランジスタの数は1.5年ごとに倍になる
アムダールの法則
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はこう解決(しようと)してる
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 の仕組み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に貯まる)
内部状態は絶対に外から見えないので、 他のスレッドから守られている
Pros & Cons• Future: 汎用的。スケールしない。
• Parallel Collection: 使い方簡単。利用機会は限定的。
• Actor: 高機能・高性能。プリミティブすぎて使いにくい (生成したら消すとか、メッセージの流れ道の組み合わせとか、全部自分でやる)。
Reactive Streamとは?
https://speakerdeck.com/okapies/reactive-streams-ru-men-number-jjug
これ見て(ごめんなさい)
Reactive Stream実装
Akka Stream
https://speakerdeck.com/okapies/reactive-streams-ru-men-number-jjug
こう書ける (らしい)
まとめ• 今後CPUの進化はマルチコア化が駆動
• CPUを使い尽くすために、並列分散プログラミングが必要
• 並列分散プログラミングは難しい
• 抽象度・一般性のトレードオフの中で、並列処理のハードルを下げる様々な概念がある
• Future, Parallel Collection, Actor, Reactive Stream