Upgrade to Pro — share decks privately, control downloads, hide ads and more …

並列対決 Elixir × C# × Go オマケでScala nodejs

enpedasi
February 23, 2018

並列対決 Elixir × C# × Go オマケでScala nodejs

大量データを非同期ストリームで並列集計処理して速度対決。それぞれの処理系の特徴を掴んで行きます。

enpedasi

February 23, 2018
Tweet

More Decks by enpedasi

Other Decks in Programming

Transcript

  1. - 登壇者のレベル 言語 経 験 node.js 納品実績あり C# 納品実績あり/非同期開発経験なし Elixir

    3年間Watch Scala ハンズオンでWebアプリ作った (Play/Skinny) Go 経験なし ここから作っていくよ! ※ 一応 C / C++ / アセンブラ 10年以上 Delphi 20年経験は あります
  2. マルチタスクモデルとGC Elixir Go C# Scala(JVM) モデ ル 軽量プ ロセス 軽量ス

    レッド スレッド プール? スレッドプ ール? GC 単位 プロセ ス 共有メ モリ 共有メモ リ 共有メモリ ※スレッドは共有メモリを使用するので、GCすると 全体が止まる
  3. 非同期ストリームのFramework Elixir Genstage/Flow Scala Akka C# TPL DataFlow Node.js Producer

    Source (ISoureBlock) reader Produce- Consumer Flow (IPropergatorBlock) (pipe) Consumer Sink (ITargetBlock) writer Scala Akka は ErlangのActor リスペクト GenStage は ScalaAkka Streams リスペクト C# TPLもActorモデル(上記の表は無理やり)
  4. Scala Akka Stream GenStageやFlowがインスパイアされたライブラリ Lightbend社(旧Typesafe社)提供 JVMで動作 Scala or Java対応 豊富なドキュメント

    Pony langageの資料では Erlangと同等性能 twitterで#Akka what's wrong?って叫んだら中の人 が10分ぐらいでPR送ってくれた(Thak you for ktoso! )
  5. result = filename |> File.stream! # データクレンジング |> Flow.from_enumerable() |>

    Flow.map( &( String.replace( &1, ",", "\t" ) ) ) # ①CSV→ |> String.replace( "\r\n", "\n" ) # ②CRL |> String.replace( "\"", "" ) ) ) # ③ダブ # 集計 |> Flow.map( &( &1 |> String.split( "\t" ) ) ) # ④タブで分割 |> Flow.map( &Enum.at( &1, 2 - 1 ) ) # ⑤2番目の項目を抽出 |> Flow.partition |> Flow.reduce( fn -> %{} end, fn( name, acc ) # ⑥同値の出現数を集計 -> Map.update( acc, name, 1, &( &1 + 1 ) ) end ) |> Enum.sort( &( elem( &1, 1 ) > elem( &2, 1 ) ) ) # ⑦多い順で
  6. チャネルによるディスパッチ func (w *worker) start() { go func() { for

    { // register the current worker into the dispatch pool w.dispatcher.pool <- w select { case v := <-w.data: if bulkstr, ok := v.([][]string); ok { wordMap := processData(bulkstr) w.dispatcher.sink <- wordMap } w.dispatcher.wg.Done() case <-w.quit: return } } }() }
  7. // チャネルからクラスタ化された集計結果を集める func waitAndSum(sumMap map[string]int, d *Dispatcher, quit for {

    select { case rec := <-d.sink: for mk, mv := range rec { sumMap[mk] += mv } case <-quit: fmt.Println("quit") return sumMap default: } } }
  8. source .via(CsvParsing.lineScanner()) .via(CsvToMap.withHeaders("firstname", "lastname", "gender" .map(_.map(r => (r._1, r._2.utf8String))) //

    Map( birthday - .filter(rec => rec.get(grp_col) != None) .groupBy(30, r => r(grp_col)(0)) // 頭一文字でグループ分け .async .fold(acc_empty) { (acc: Map[String, Int], rec: Map[String val word = rec(grp_col) val cnt = acc.getOrElse(word, 0) acc.updated(word, cnt + 1) } .mergeSubstreams .via(Flow[Map[String, Int]].fold(acc_empty) { (acc: Map acc ++ rec.map { case (k, v) => k -> (v + acc.getOrElse(k, }) .runWith(Sink.foreach(e => {resultMap = e})) .onComplete(done ⇒ { val msec = (System.currentTimeMillis - start) println(done) resultMap.toSeq.sortWith(_._2 > _._2).take(10).foreach(pri
  9. Node.js(抜粋) SourceCode on Github const parser = csvParse({ delimiter: ',',

    relax_column_count: true }); parser.on('data', (data) => { const key = data[1] sumMap.set(key, sumMap.has(key) ? sumMap.get(key) + 1 : 1) }); ストリームをラップしたcsv-parserを使用 CSV parserが行ごとにdataイベントを返すので、 都度集計処理
  10. C# dotnet core 2.0 実装 抜粋 SourceCode on Github static

    private async Task<ConcurrentDictionary<string, int>> Rea { using (FileStream sourceStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 4096 )) { StreamReader sr = new StreamReader(sourceStream) string text; while ((text = await sr.ReadLineAsync()) != { var wd = text.Replace("\"", string.Empty).Spli wordMap.AddOrUpdate(wd, 1, (_key, val) => { } return wordMap; } }
  11. Windows10 Home edition Corei5 [email protected] Hz 2コア4スレッド Elixir Go C#

    Scala Node.js 30 万 行 0.3 1.2 1.5 3.7 10.5 300万行 19 12 15 27 107 1200万行 86 50 47 101 433 単位(秒) C#とGoが速い ※ScalaはIntelliJ環境下のスコア
  12. Windows Server 2008 R2 8GB Intel Xeon(R) CPU E31230 (32n

    2011Q2)@ 3.2GHz 4 コア8スレッド (On-premises) Elixir Go C# Scala Node.js 30万行 300万行 1200万行 49 43 35 コア数が増えたことで、85秒⇒49秒へ猛追 C#も速度上がる。Windows上ではC#最速
  13. ここでLinuxでC#に問題発生 Unhandled Exception: System.ArgumantException The index is equal to or

    greater than the length of the array, or the number of elements in dictionnary is greater than the available space from idex to the end of destination array.
  14. GCE Debian 9 vCPU 8 16GByte Elixir Go C# Scala

    Node.js 30万行 0.09 1.2 Crush - 13 300万行 9.6 11.4 Crush 28 137 1200万行 40.1 44.8 Crush 116 549 ElixirがGoを抜きさる C#は例外クラッシュ Node.jsはWindowsより1割ほど速度低下 ScalaはCPUあんまり使ってない。プログラムが問 題かも。
  15. Docker 2GB/2Core Debian Microsoft公式 dotnetcoreコンテナ Elixir Go C# Scala Node.js

    30万行 0.3 2.2 Crush 300万行 29 23 Crush 1200万行 118 Killed Crush C#は例外クラッシュ Goはメモリ不足でTask Kill
  16. Elixir/Go/C# ともにNode.jsの5 - 10倍の速度 Elixir Flow ⇒コア数が多いほど真価を発揮 ⇒コード量の少い!学習コスト少ない!性能高い! どの環境でも安定度が抜群 ⇒Shift-JIS対応や商用RDBとの接続に課題

    GO ⇒並列入門用としては最適 ⇒GoroutineはGCされないので、メモリ管理が負 担。1200万件では8GBのメモリを消費 ⇒コード量がかさむ。大規模PJでは辛そう ⇒VS CodeのIDE力が強力
  17. Elixir Go C# Scala Node.js パフォーマンス B+ ~A A A

    B+? D ポーテーション 〇 〇 ? 〇 〇 安定度 S B A A A 環境構築コスト A S B C A 学習コスト B+ A B+ B- A 記述量 S C A S A メンテナンス性 A B A A A ドキュメント B+ A A S A Elixirの安定度がSなのはGCの仕組による(プロセ ス単位)