Upload
sho-hosoda
View
705
Download
6
Tags:
Embed Size (px)
DESCRIPTION
関数型スタイルでのクイックソートを高速化した話(Ruby)です。KLab ALM 2014/07/29 で発表しました。
Citation preview
Immutable List Gem
2014/07/29 KLab 社内勉強会 ALM
細田 翔 (@gam0022)
自己紹介
細田 翔
筑波大学情報学群情報科学類 B4
- 非数値処理アルゴリズム研究室
15卒 KLab内定者
Twitter: @gam0022
http://gam0022.net/
2
話す内容
関数型スタイルでのクイックソートを高速化した話(Ruby)
3
クイックソート
念のためにおさらい
クイックソートの基本的な考え
- データを軸要素(ピボット)より大きい要素・小さい要素で分割することを再帰的に繰り返してソートを行なう
4
http://www.ics.kagoshima-u.ac.jp/~fuchida/edu/algorithm/sort-algorithm/quick-sort.html
Rubyでクイックソート
手続き型スタイルで実装した場合
特徴
1. コードが長い(tempなどの変数が必要)
2. 昇順降順の切り替えに2箇所の変更
3. 動作が追いにくい
5
OCamlでクイックソート
OCaml (関数型言語)による実装
特徴
1. 5行で書ける!
2. 昇順降順の切り替えは1箇所の変更
3. 動作が追いやすい
6
Ruby vs OCaml
Ruby vs OCaml
- OCaml の圧倒的な勝利
Ruby でも OCaml のようなクイックソートを書きたい!
7
Ruby OCaml
行数 29 5
昇順降順の変更箇所 2 1
可読性 △ ○
Ruby でクイックソート その2
OCaml Ruby
左: OCamlの5行のコードを List.partition を使わないで再実装
右: 左のコードを Ruby に移植したコード
Ruby でも関数型スタイルにプログラミングできた!8
だが待って欲しい!
Ruby でクイックソートその2
このコードは確かに動作する
問題点
- Array#+ や Array#drop は非破壊的メソッド
- Array のインスタンスを大量に生成
- Array を何度もコピー
10
処理コストが大きい!
データ構造を見直す
関数型スタイルのプログラミングには、Array(配列)は適さない
データ構造から見なおしてみる
(Immutable) Singly Linked List が最適
- イミュータブルな単方向連結リスト
- OCaml や Lisp の List にも使われる
12
Immutable Singly Linked List を実装
普通に Ruby で実装してもネタとして面白くない
Array は組み込みクラスであり、C言語で実装されているので、普通にRubyで実装してもパフォーマンス的にも勝ち目がない
「C拡張」として、実装しよう!
13
Ruby の C拡張
C拡張
- C言語で書いたコードをリンクして Ruby から利用する仕組み
部分的な処理の高速化・ラッパーライブラリなどに使われる
例:C拡張を利用した gem
- スクレイピングライブラリの nokogiri
- SQlite3 のラッパーライブラリの sqlite3
14
ImmutableList の実装
クラス名は ImmutableList
struct immutable_list の定義 (Cの構造体をラップ)
15
ImmutableList の実装
OCaml を意識したメソッドを実装
- cons
- head, tail
- rev_append, rev, append
- length
- nth
- inspect
16
ImmutableList の実装
cons (先頭への要素追加) の定義
17
ImmutableList の実装
全部で200行くらいで実装できた
罠が多くて苦労した
- 明示的にオブジェクトをmarkしないと GC で勝手にオブジェクトが開放されて SEGV が発生したりとハマりどころが多い
- Ruby 処理系の勉強にはすごくなった
18
Ruby でクイックソート その3
ImmutableList を使用して再実装
19
Ruby でクイックソート 考察
全く同じ処理
クラスを置き換えただけ
性能に違いが出るか?
20
Array ImmutableList
Array ImmutableListfirst head
drop(1) tail[i]+a a.cons(i)
ベンチマーク
データの長さn と 処理時間(CPU時間) の関係を調査
ソートデータ: 重複のないランダムなデータを使用
最新の Ruby 2.1.2 を使用
21
結果
22
リストの長さのクイックソートの実行時間
実行時間[sec]
0
0.1
0.2
0.3
0.4
リストの長さ[個]0 2000 4000 6000 8000
Array ImmutableList
リストの長さに比例して、ImmutableList が高速になる
最大で4.3倍の高速化!
まとめ
処理によっては、再帰を用いた関数型スタイルでプログラミングすることで、シンプルに実装できる場合がある
パフォーマンス的には、Ruby の Array は関数型スタイルには適していない
C拡張として実装した ImmutableList によって、関数型スタイルでのクイックソートを高速化することができた!
23
RubyGems に公開中
RubyGems は Ruby ライブラリの管理システム
immutable_list の名前で登録済みなので、コマンド一発で導入可能
24
ご静聴ありがとうございました
おまけ
本当にC拡張で作った意味があったので疑問だったので、Rubyでも同じ機能を持つクラスを実装した
ソースコードはC実装の1/3くらいになった(Rubyの生産性すごい)
次のベンチマークをとった
- 先ほどのクイックソート
- 単純な連結(append)処理
26
ImmutableListのRuby実装と比較
Ruby で実装した ImmutableList(Ruby)
手続き型スタイルのクイックソート Procedural
27
リストの長さとクイックソートの実行時間
実行時間[sec]
0
0.1
0.2
0.3
0.4
リストの長さ[個]0 2000 4000 6000 8000
Array ImmutableListImmutableList(Ruby) Procedural
C拡張版の方が少し高速
ImmutableListのRuby実装と比較
連結の回数と実行時間(対数グラフ)
実行時間[sec]
0.01
0.1
1
10
100
連結回数[回]1000 10000 100000
Array ImmutableListImmutableList(Ruby)
長さ6のリストをn回連結
28
ImmutableListのRuby実装と比較
C拡張版の方が高速で良かった
でも、思ったよりは変わらなくて残念(́・ω・`)
関数型スタイルは手続き型スタイルよりすごく遅い
29
Immutable List が Array より メモリの消費が少ない説明
Arrayの連結
C = A+B を行なった時、AとBの内容を全てコピーする
1 2 3 4 5 6 7 8+
A B
1 2 3 4 5 6 7 8A B
1 2 3 4 5 6 7 8C
Immutable Singly Linked List の連結
C = A+B を行なった時、Bとの差分だけがコピーされる
1 2 3 4 5 6 7 8+
A B
1 2 3A
1 2 3C
4 5 6 7 8B
head と tail
次の操作は参照を返すだけなので、とても高速
- head: 先頭の要素の取り出し Array#first に対応
- tail: 2番目以降のリストの取り出し Array#drop に対応
33
1 2 3 4 5 6 7 8
tail
head