Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
速いクイックソート: Pattern-defeating quicksort
Search
WATANABE Yuki
August 24, 2022
Programming
0
380
速いクイックソート: Pattern-defeating quicksort
社内勉強会から。Pattern-defeating quicksort アルゴリズムの大まかな説明。
WATANABE Yuki
August 24, 2022
Tweet
Share
More Decks by WATANABE Yuki
See All by WATANABE Yuki
C++ はなぜあんなにも複雑なのか
magicant
0
82
鉄道シミュレーターで自動運転を実装した話
magicant
0
300
ドキュメント、書けてますか?
magicant
0
120
Other Decks in Programming
See All in Programming
CSC305 Lecture 06
javiergs
PRO
0
250
Writing Better Go: Lessons from 10 Code Reviews
konradreiche
0
1.3k
品質ワークショップをやってみた
nealle
0
510
なぜGoのジェネリクスはこの形なのか? Featherweight Goが明かす設計の核心
ryotaros
7
1.1k
CSC305 Lecture 04
javiergs
PRO
0
270
CSC509 Lecture 05
javiergs
PRO
0
300
The Past, Present, and Future of Enterprise Java
ivargrimstad
0
400
詳しくない分野でのVibe Codingで困ったことと学び/vibe-coding-in-unfamiliar-area
shibayu36
3
5.1k
スマホから Youtube Shortsを見られないようにする
lemolatoon
27
33k
Software Architecture
hschwentner
6
2.3k
What Spring Developers Should Know About Jakarta EE
ivargrimstad
0
200
非同期jobをtransaction内で 呼ぶなよ!絶対に呼ぶなよ!
alstrocrack
0
970
Featured
See All Featured
A Tale of Four Properties
chriscoyier
161
23k
Keith and Marios Guide to Fast Websites
keithpitt
411
23k
jQuery: Nuts, Bolts and Bling
dougneiner
65
7.9k
Speed Design
sergeychernyshev
32
1.2k
How to Create Impact in a Changing Tech Landscape [PerfNow 2023]
tammyeverts
55
3k
Bootstrapping a Software Product
garrettdimon
PRO
307
110k
YesSQL, Process and Tooling at Scale
rocio
173
14k
Unsuck your backbone
ammeep
671
58k
RailsConf & Balkan Ruby 2019: The Past, Present, and Future of Rails at GitHub
eileencodes
140
34k
Building Better People: How to give real-time feedback that sticks.
wjessup
369
20k
Improving Core Web Vitals using Speculation Rules API
sergeychernyshev
21
1.2k
Faster Mobile Websites
deanohume
310
31k
Transcript
速いクイックソート 渡邊裕貴 / 2022-08-24
Pattern-defeating quicksort • Orson Peters という人が 2015 年ごろに発明した不安定ソートアルゴリズム • クイックソートを速くするいろいろな工夫の集大成みたいな感じ
• オリジナルのコードは C++ で書かれている • Rust の標準ライブラリーで採用された • 最近 Go の標準ライブラリーでも採用された
1分でわかる普通のクイックソート 5 4 1 6 7 3 2 9 8
配列の中から一つ軸を選ぶ (pivot) 2 4 1 3 5 7 6 9 8 軸未満→軸→軸以上 の順になるように入れ替える 以下、前後それぞれ繰り返し 2 4 1 3 5 7 6 9 8 1 2 4 3 5 6 7 9 8
クイックソートの計算量 全ての分割が二等分になる場合が最も効率的で、O(n log n) 4 3 7 6 1 5
2 2 3 1 4 6 5 7 1 2 3 4 5 6 7 n log n
クイックソートは遅い時もある (1) 常に配列の先頭を軸にする場合、配列が元からソート済みだと分割が偏る 1 2 3 4 5 6 7
n 1 2 3 4 5 6 7 1 2 3 4 5 6 7 1 2 3 4 5 6 7 n O(n2)
軸の選び方の工夫 • 配列の先頭 • 配列の真ん中 • 三つ選んで中央値を取る ◦ 3 回の比較でできる
• John Tukey’s ninther ◦ 「三つ選んで中央値を取る」を 3 回やって中央値を取る ◦ 12 回の比較でできる 簡単 分割が偏りにくい
クイックソートは遅い時もある (2) 同じ値がたくさんある場合、分割が偏る 1 1 1 1 1 1 1
n 1 1 1 1 1 1 1 1 1 1 1 1 1 1 n 1 1 1 1 1 1 1 どれを選んでも軸は 1 O(n2)
軸に等しい値の振り分けを工夫する 1 1 2 1 1 1 1 1 1
2 1 1 1 1 新しい軸が前回の軸と等しいなら 同じ値がたくさんある公算が高い 軸に等しい値→軸より大きい値 の順に入れ替える 軸に等しい値どうしはもうソートしなくていい O(n2) が O(n) になった 1 1 1 1 1 1 2
クイックソートは遅い時もある (3) それでも分割が偏る可能性はゼロではない • とても運が悪かった場合 • 分割が偏るように仕組まれている場合
軸の候補をシャッフルする 分割が偏っていた場合 (分割比が 1:7 未満の場合) は 次の軸の選び方 (John Tukey’s ninther
で使う値) を変える • オリジナル版: ◦ 配列の端っこや真ん中だけじゃなくて四等分した位置から選ぶ • Rust/Go 版: ◦ 四等分した位置だけじゃなくてランダムに選ぶ ▪ 簡易的な乱数を使う
最後の手段: ヒープソート 偏った分割が log n 回発生したら、諦めてヒープソートに移行する ヒープソートとは: • 二分木と等価な構造を配列上に再現するソート手法 •
クイックソートより 2 倍くらい遅い • 最悪計算量 O(n log n) が保証されている
部分挿入ソートによる高速化 • 以下の条件が全て満たされたとき、配列全体がソート済みの可能性が高そう ◦ 前回の分割比が 1:7 より悪くなかった ◦ 前回の分割時、軸の前後で入れ替わった要素がなかった ◦
John Tukey’s ninther で軸を選ぶとき候補となる九つの値がソート済みだった • この場合は挿入ソートを試す ◦ ただし一定ループ回数で終わらなければ諦める (O(n))
配列の分割 (軸前後の値の入れ替え) の遅さ 典型的コード while (left < right && array[left]
< pivot) left++; while (left < right && array[right] >= pivot) right--; if (left < right) { tmp = array[left]; array[left] = array[right]; array[right] = tmp; } 分岐予測の外れやすい分岐 1 回の入れ替えで 3 回の代入
BlockQuicksort による高速化 1. 配列の先頭 128 個と末尾 128 個を一気に軸と比較して 入れ替えるべき値の位置を一時配列にまとめる ◦
ループ回数を固定したことで分岐予測が当たるようになる ◦ ループアンローリングも可能に 2. 一次配列に入った位置の値を一気に入れ替える ◦ 2n 個の値を 2n+1 回の代入で入れ替える tmp
まとめ Pattern-defeating quicksort にはソートを速くするいろんな工夫が詰まっている • 中央値に近い値を軸にする: John Tukey’s ninther •
軸に等しい値を効率よく集める • 軸の候補をシャッフルする • 再帰回数が O(log n) を超えそうならヒープソート • 既にソート済みっぽいなら挿入ソート • 値の入れ替えを速くする: BlockQuicksort