Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

今日からはじめるGPars

「第16回 G*ワークショップ+JGGUG総会」
発表資料
http://kokucheese.com/event/index/12173/

  • Be the first to comment

今日からはじめるGPars

  1. 1. 今日からはじめる GPars 2011-06-17第16回 G*ワークショップ 吉田 健太郎 (@fumokmm)
  2. 2. 自己紹介名前:吉田 健太郎(ふも)仕事:プログラマ!Twitter:@fumokmmはてな:id:fumokmmブログ:No Programming, No Life    http://d.hatena.ne.jp/fumokmm/活動:● JGGUG運営委員● Grailsドキュメント会参加● G*Magazine vol.2「もし新人女子Javaプログラマが 『Groovyイン・アクション』を読んだら」
  3. 3. GParsとはなにか Groovy Parallel Systemshttp://gpars.codehaus.org/ 読み方は「じーぱーず」
  4. 4. GParsとはなにかGParsはGroovyベースの並列処理ライブラリ群他の言語の並列処理で使われている技術のいいとこどりをしていったらすごいことになってしまったライブラリ。ほとんどJavaで書かれてるから、速度は問題ないJavaからも使える。(若干面倒だけど)Groovy v1.8から本体にバンドルされるようになったのはGPars v0.11 で、最新版はv0.12。v1.8な人はインポートするだけで使える。そうじゃない人は、@Grabればいい。@Grab(org.codehaus.gpars:gpars:0.12)
  5. 5. 本日のメニュー非同期コレクション▶アクターエージェントデータフロー       まとめ
  6. 6. 非同期コレクション
  7. 7. 並列コレクションとはコレクションに対する並列処理を簡単に提供。並列コレクションはJavaの並列処理ライブラリのラッパーを提供。● 並列コレクションは JSR 166● Fork/Join は JSR 166y (Java 7)● Map/Filter/Reduce
  8. 8. 並列コレクションなハロワimport static groovyx.gpars.GParsPool.*def numbers = [1, 2, 3, 4, 5, 6]def squares = [1, 4, 9, 16, 25, 36]withPool { assert squares == numbers.collectParallel { it * it }} parallel_collection01.groovyまず、並列処理したい部分をwithPoolで囲む。ここの例では parallel に collect している。
  9. 9. parallelにeachしてみるimport static groovyx.gpars.GParsPool.*def numbers = 0..9withPool { numbers.eachParallel { print it }} parallel_collection02.groovy 出力例(毎回異なる): 0213756489 Result: 0..9
  10. 10. parallelメソッド対応表通常のメソッドと withPool内で使えるようになる parallel なメソッドの対応表
  11. 11. withPoolについてimport static groovyx.gpars.GParsPool.*withPool(10) { // 10スレッド分スレッドが入るプールを確保 // この中で自由にスレッドを泳がせてね}withPoolはデフォルトではコア数+1のスレッドを使う。第一引数でスレッド数を指定可能。例) Core 2 な場合、デフォルトではスレッド数は3。
  12. 12. makeTransparentimport static groovyx.gpars.GParsPool.*withPool { def numbers = [1, 2, 3, 4, 5, 6].makeTransparent() def squares = [1, 4, 9] assert squares == numbers.collect{ it * it } .grep{ it < 10 }} parallel_collection03.groovy.makeTransparent() しておくと、いちいち 〜Parallelしなくて済むからちょっとだけシンプルに書ける。※注意※ v0.12から名前が .makeConcurrent() に変更になっています。
  13. 13. map/filter/reduceimport static groovyx.gpars.GParsPool.*withPool { assert 30 == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].parallel .filter { it <= 5 } .map { it * 2 } .reduce { a, b -> a + b }} parallel_collection04.groovyコレクションに .parallel を付けると、関数型っぽくmap/filter/reduceできるようになります。
  14. 14. map/filter/reduceメソッド対応表
  15. 15. 例:文字数カウントimport static groovyx.gpars.GParsPool.*def words = "This is just a plain text tocount words in"print count(words)def count(arg) { withPool { return arg.parallel .map{[it, 1]} .groupBy{it[0]}.getParallel() .map {it.value=it.value.size();it} .sort{-it.value} .collection} } parallel_collection05.groovy結果はラップされているので、最後に .collection を付ける必要がある。.groupByなどたまに対応してないメソッドがあるので、.getParallel() しなおしてあげる必要がある。
  16. 16. 例:スリープソートimport static groovyx.gpars.GParsPool.*import java.util.concurrent.*def numbers = [8, 1, 4, 9, 3, 6]def latch = newCountDownLatch(numbers.size())// numbersのサイズ分、スレッド確保withPool(numbers.size() ?: 1){ numbers.eachParallel { latch.countDown() latch.await() // 全部の準備が終わるまで待つ Thread.sleep((it as int) * 10) println it }} sleepSortSample.groovy cf. http://d.hatena.ne.jp/fumokmm/20110611/1307811572
  17. 17. 例:フィボナッチ数 (Fork/Join)import static groovyx.gpars.GParsPool.*def fibo(num) { withPool { runForkJoin(num) { n -> switch (n) { case 0..1: return n default: forkOffChild(n - 1) forkOffChild(n - 2) [*childrenResults].flatten().sum()} } } }assert fibo(10) == 55 fibonacciSample.groovyJSR 166yのfork/join機能をGParsのラッパーから使う例。
  18. 18. M/F/R vs Fork/Join
  19. 19. 本日のメニュー非同期コレクションアクター▶エージェントデータフロー       まとめ
  20. 20. アクター
  21. 21. アクターとは メッセージ 返信 基本ステップアクターはそれぞれ、独立したスレッド(世界)で動く。自分とは別のアクターとやり取りするには、メッセージを送る。メッセージを受け取ったアクターはメッセージに返信することができる。
  22. 22. アクターなハロワimport static groovyx.gpars.actor.Actors.*def console = actor { loop { react { msg -> println msg } }}console.send(Hello World!) actor01.groovyアクターを生成するには、actor { … } DSLを使う。loopはstop()されるまで、繰り返される。最後に、consoleアクターに Hello World! という Stringをメッセージとして送信している。
  23. 23. .send の別記法console.send(Hello World!)console << Hello World! // << でもOKconsole.call(Hello World!) // .callでもOKconsole Hello World! // .call=クロージャ呼び出しメッセージ送信のためのメソッド .send には別記法が用意されている。(種類は上記コード参照)ErlangやScalaなどで使う ! は Groovyではオーバーロードできないので違う記法を採用している。
  24. 24. メインスレッドがアクターに送信import static groovyx.gpars.actor.Actors.*def console = actor { loop { react { msg -> println msg } }}// 出力順はばらばらconsole << Hello World!println finish. actor02.groovy
  25. 25. メインスレッドがアクターに送信import static groovyx.gpars.actor.Actors.*def console = actor { loop { react { msg -> println msg } }}console << Hello World!console.stop() // <- consoleのアクターをストップconsole.join() // <- メインスレッドで、console         // アクターが終わるまで待つprintln finish. actor03.groovy
  26. 26. 高度なloopdef whileTrue = {-> // 繰り返し条件を返すクロージャ(真の間繰り返す)}def printResult = {-> // ループ終了時に呼び出されるクロージャ}loop(whileTrue, printResult) { // アクターに行わせる処理} actor04.groovyloopの第一引数に繰り返し条件(真の間繰り返し)を返すクロージャやint(繰り返し回数)を指定することでループ回数を制御できる。第二引数にクロージャを渡すと、loop終了時に実行される(フック処理)
  27. 27. reactor (リアクター)import static groovyx.gpars.actor.Actors.*def console = actor { loop { react { msg -> println msg } }}// 同じdef console2 = reactor { msg println msg } actor05.groovyloop-react の組み合わせはよく使うので用意された便利DSL。リアクション専用。
  28. 28. sender(送り主)react { tweet -> if (isSpam(tweet)) { ignoreTweetsFrom sender // 通報 } sender.send 二度と送って来ないでね!}senderはactorのクロージャ内部で、送信元を特定するのに使える。
  29. 29. reply(返信)import static groovyx.gpars.actor.Actors.*def replyingActor = reactor { msg -> println "受信: $msg" reply "<<$msg>>"}def reply = replyingActor.sendAndWait(メッセージ 1)assert reply == <<メッセージ1>> actor06.groovyここでは、msgを受け取って、<< と >> で囲ったものを返却している。.sendAndWait はメッセージを送信した後、アクターからの返信を待つ。
  30. 30. メッセージ振り分け(switch-case)loop { react { text -> switch(text) { case String : reply << "あなたの送ったのはString" break case Integer: reply << "あなたの送ったのはInteger" break } }}一番単純な振り分け。受け取ったメッセージをswitch-case式を用いて振り分けている。Groovyのswitch-caseはそれなりに強力なのでこれでも十分強力かもしれないが、もっといい方法がある。
  31. 31. メッセージ振り分け(when)import static groovyx.gpars.actor.Actors.*def handler = messageHandler { when { String message -> reply 文字列 } when { Number message -> reply 数字 }}assert 文字列 == handler.sendAndWait(こんにちは)assert 数字 == handler.sendAndWait(123) actor07.groovyMessageHandler { … } DSLを使うと when に渡すクロージャのバリエーションでメッセージを振り分けることができる。
  32. 32. 比較:Scalaアクターの メッセージ振り分けval badActor = actor { var done = false while (! done) { receive { case NegotiateNewContract => // 処理 case Speak(line) => // 処理 case _ => // 全部マッチしなかった時の処理 } }}badActor ! NegotiateNewContractbadActor ! Speak("Do ya feel lucky, punk?"
  33. 33. メッセージについてGParsのメッセージはイミュータブルにする必要はない(強制されてはいない)が、送信したあとはむやみに触るべきではない。他の言語(Erlang)とかはそもそもすべてがイミュータブルなので、ここらへんは心配しなくてよい。Scalaはcase classにしたりして、パターンマッチでメッセージを受け取っている。(1ページ前の比較を参照)
  34. 34. 本日のメニュー非同期コレクションアクターエージェント▶データフロー       まとめ
  35. 35. エージェント
  36. 36. エージェントとは並列処理環境においてミュータブルなデータを安全に取り扱うのは難しい。そこでエージェントを使い、データの更新をラップする。この機能はClojure由来。
  37. 37. エージェントなハロワimport groovyx.gpars.agent.Agentdef guard = new Agent<String>()guard { updateValue(GPars) }guard { updateValue(it + is groovy!) }assert GPars is groovy! == guard.val agent01.groovyエージェントが内包する値の更新は、クロージャを通して行うことになる。結果値は .val で取得できる。
  38. 38. リストに追加import groovyx.gpars.agent.Agentdef members = new Agent([Me])members.send {it.add A}def t1 = Thread.start { members.send {it.add B } }def t2 = Thread.start { members << { it.add C } members { it.add D } }[t1, t2]*.join()println members.valmembers.valAsync {println "現在のメンバ: $it" }members.await() agent02.groovy複数スレッドから同時に更新をかけてもリストは壊れない。エージェントが守っているから。
  39. 39. リスナーとバリデータを追加import groovyx.gpars.agent.Agentdef counter = new Agent()counter.addListener{ oldVal, newVal -> println "$oldVal -> $newVal"}counter.addValidator{ oldVal, newVal -> if (newVal < oldVal) throw new Exception(新しい値が小さいのでエラー)} . . .assert counter.hasErrors()assert 1 == counter.errors.size() agent03.groovy値の更新時に通知したり、チェックしたりするクロージャを追加することができる。エラーがあったかは後でチェック。
  40. 40. 本日のメニュー非同期コレクションアクターエージェントデータフロー▶       まとめ
  41. 41. データフロー
  42. 42. データフローとはタスクという単位に処理を切り分けて記述してゆく。タスクの順番的な依存関係はデータフローがいい感じに解決してくれる。
  43. 43. データフローなハロワimport groovyx.gpars.dataflow.DataFlowsimport static groovyx.gpars.dataflow.DataFlow.*def flow = new DataFlows()task { flow.result = flow.x + flow.y } //task { flow.x = 10 } //task { flow.y = 5 } //assert 15 == flow.result // dataflow01.groovy①、②、③の順番で代入が実行される。②の flow.result は ①の flow.x, flow.y が代入されるまで待機する③の flow.result は ①の flow.result が代入されるまで待機する
  44. 44. デッドロックにご用心import groovyx.gpars.dataflow.DataFlowsimport static groovyx.gpars.dataflow.DataFlow.*def flow = new DataFlows()task { flow.x = flow.y }task { flow.y = flow.x } // デッドロック!println flow.x // 帰ってこない dataflow02.groovyデッドロックが発生する例。flow.x のようにしなければ、帰ってくる。
  45. 45. 本日のメニュー非同期コレクションアクターエージェントデータフロー       まとめ ▶
  46. 46. まとめGParsでだいたいどんなことができるかわかって頂けたかと思います。エージェントとデータフローの内容が薄いのはまとめきれてないだけです・・・(反省)本当はもっと多機能なので、ご興味が湧かれた方はぜひ調べてみて下さい。GParsで日常の小さな部分からコツコツと簡単に並列処理化をはじめてみませんか?
  47. 47. まとめGParsはクロージャ、DSL、MOPなどのGroovy機能を駆使してJVMの強力な並列処理機能をシンプルに使いこなすGroovy流のやり方だった
  48. 48. 参考● Groovy in Action, Second Edition http://www.manning.com/koenig2/● GPars Javadoc http://gpars.org/0.12/javadoc/● GPars ユーザガイド&リファレンス http://gpars.org/0.12/guide/index.html
  49. 49. 告知:GParsドキュメント翻訳中 現在GParsユーザガイド(v0.12)の翻訳作業を Google Translator Toolkit上で行っています。      http://goo.gl/d1LHM 参加者:ふも(@fumokmm)     杉浦さん(@touchez_du_bois) 協力者大募集中!   @fumokmm までご連絡下さい。
  50. 50. ご清聴ありがとうございました

×