LoginSignup
0
1

More than 5 years have passed since last update.

基数ソート及びマージソート並びに標準関数のqsortの性能評価

Last updated at Posted at 2017-08-31

ソースコード

ソースコードはゴミ置き場に置いてある
https://github.com/2a3oiUA3zfDtr3py/Misc/tree/master/Sort_Test

実験したアルゴリズムとデータ型

データ型
  • uint32_t型 (32Bit符号なし整数)
  • uint64_t型 (64Bit符号なし整数)
  • float型 (32Bit実数)
アルゴリズム
  • 標準ライブラリ qsort
  • 非再帰のマージソート
  • LSD方式の基数ソート 8bit毎に処理
  • LSD方式の基数ソート 16bit毎に処理
  • MSD方式の基数ソート 16bit毎に処理
  • 雑種(8bit LSD + 8bit MSD + マージ)

符号なし整数の実験結果から、MSD方式及びLSD方式の基数ソート並びにマージソートを組み合わせたハイブリッド方式を作った

実数を扱う場合、NaNの含まれたデータはどの様にソートするべきか?
正の無限大より大きな数字として見做すべきか、負の無限大より小さな数字と見做すべきか?
ハイブリッド方式は実数はデータにNaNが含まれている場合は取り敢えず最後に持っていく事にした。
マージソート、基数ソート、qsortはNaNが無いものと仮定してソートするようしてある

x MBのデータを読み込みN 分割したN個の長さy = x/(N*sizeof())の配列をソートをN回行うプログラムの実行時間を調べた

データは次のように生成した
整数
head -c 536870912 /dev/urandom > temp.bin
実数はBox–Muller法で生成した数値の逆数を出力するプログラムを作った
./generator 134217728 temp.bin

実験環境

Linux 4.12 Gentoo
gcc version 6.4.0 (Gentoo Hardened 6.4.0 p1.0)
作業ディレクトリ /dev/shm
作業マシン:今は死に体の東芝製 dynabook TX/66J

実験結果

複数回実行した中央値のリスト
単位は秒

512MB 32Bit整数

配列の要素数 * 分割数 qsort Merge Radix 16 Radix 8 Radix MSD Hybrid
134217728 * 1 41.95 28.51 11.30 7.10 25.02 4.31
67108864 * 2 40.71 26.13 8.59 7.23 42.24 4.23
33554432 * 4 37.55 25.44 7.63 7.08 79.54 3.95
16777216 * 8 35.94 24.15 7.26 6.60 154.14 3.79
8388608 * 16 35.08 23.50 7.13 6.16 300.04 3.64
4194304 * 32 32.80 23.11 7.00 5.57 3.53
2097152 * 64 31.45 21.85 6.95 4.93 3.41
1048576 * 128 31.08 21.19 6.18 4.33 3.23
524288 * 256 28.04 20.11 4.00 3.67 3.12
262144 * 512 27.79 18.83 3.20 2.72 2.96
131072 * 1024 25.43 17.61 3.11 2.45 2.73
65536 * 2048 23.61 16.72 3.47 2.36 2.64
32768 * 4096 22.45 15.54 4.14 2.34 2.61
16384 * 8192 20.84 14.51 5.59 2.34 2.63
8192 * 16384 19.45 13.29 8.55 2.36 2.65
4096 * 32768 18.09 12.11 14.65 2.34 2.62
2048 * 65536 16.94 11.35 27.76 2.40 2.68
1024 * 131072 15.40 10.26 2.58 2.84
512 * 262144 14.03 9.51 2.79 3.82
256 * 524288 12.67 8.36 3.68 3.96
128 * 1048576 11.32 7.63 5.31 5.69
64 * 2097152 9.87 6.92 8.55 7.15

16MB 32Bit整数

配列の要素数 * 分割数 Radix 16 Radix MSD
4194304 * 1 17.88
2097152 * 2 35.48
1048576 * 4 70.79
524288 * 8 138.86
262144 * 16 252.94
131072 * 32 313.03
65536 * 64 283.12
32768 * 128 185.39
16384 * 256 109.16
8192 * 512 59.22
4096 * 1024 31.03
2048 * 2048 16.29
1024 * 4096 1.62 9.02
512 * 8192 3.19 6.21
256 * 16384 6.30 6.48
128 * 32768 12.53 9.90
64 * 65536 24.93 18.25

512MB 64Bit整数

配列の要素数 * 分割数 qsort Merge Radix 16 Radix 8 Hybrid
67108864 * 1 22.16 13.61 12.71 7.99 4.22
33554432 * 2 21.67 13.98 9.84 8.27 4.32
16777216 * 4 23.43 13.14 8.71 8.27 3.92
8388608 * 8 19.85 13.17 8.16 7.80 3.87
4194304 * 16 21.70 12.10 8.03 7.33 3.85
2097152 * 32 20.68 11.80 7.99 6.84 3.92
1048576 * 64 17.53 10.99 7.95 6.33 3.87
524288 * 128 16.57 10.74 7.17 5.76 3.68
262144 * 256 16.36 10.09 5.05 4.79 3.65
131072 * 512 15.09 9.11 3.80 3.29 3.66
65536 * 1024 13.00 8.49 3.83 3.06 3.15
32768 * 2048 12.99 8.04 4.61 3.03 3.13
16384 * 4096 12.24 7.46 6.24 3.04 3.12
8192 * 8192 10.58 7.03 9.47 3.06 3.12
4096 * 16384 10.16 6.45 15.54 3.08 3.17
2048 * 32768 9.14 5.94 28.58 2.90 3.01
1024 * 65536 8.31 5.38 54.27 3.03 3.11
512 * 131072 7.69 4.97 3.42 3.49
256 * 262144 6.88 4.42 3.92 4.02
128 * 524288 6.16 4.01 5.77 4.08
64 * 1048576 5.36 3.53 8.94 3.75
32 * 2097152 4.60 3.21 15.04 3.56

16MB 64Bit整数

配列の要素数 * 分割数 Radix 16
512 * 4096 3.19
256 * 8192 6.31
128 * 16384 12.53
64 * 32768 25.01
32 * 65536 50.49

512MB float型

配列の要素数 * 分割数 qsort Merge Radix 8 Hybrid
134217728 * 1 40.89 28.41 5.87 3.83
67108864 * 2 39.55 28.53 6.08 3.84
33554432 * 4 37.90 27.75 5.99 3.86
16777216 * 8 36.56 26.08 5.74 3.83
8388608 * 16 35.14 25.28 5.43 3.82
4194304 * 32 33.50 23.93 5.20 3.82
2097152 * 64 32.12 23.36 4.85 4.10
1048576 * 128 30.56 22.03 4.58 3.77
524288 * 256 28.68 21.10 4.04 3.66
262144 * 512 27.14 19.45 3.19 3.40
131072 * 1024 25.70 18.33 2.96 3.25
65536 * 2048 24.33 17.38 2.94 3.16
32768 * 4096 22.96 16.20 2.92 3.15
16384 * 8192 21.57 15.27 2.93 3.16
8192 * 16384 20.17 14.34 2.91 3.16
4096 * 32768 18.77 12.98 2.79 3.04
2048 * 65536 17.42 12.24 2.87 3.08
1024 * 131072 16.07 10.93 3.03 3.28
512 * 262144 14.72 10.11 3.25 3.54
256 * 524288 13.32 8.91 4.11 4.58
128 * 1048576 11.97 8.04 5.90 6.35
64 * 2097152 10.54 7.10 9.29 8.02
32 * 4194304 9.12 6.35 15.80 7.42

考察

処理速度

16bitごと、8bitごとの基数ソートの速度差はデータの書き込みのランダムさや初期化コストの差の影響かな?
配列Aから一つのデータを読み取り、基数に基づいて配列Bのある地点に書き込むという処理について考える
配列Aから読み込んだデータの一部分8bit分と16bit分の考えられるパターンは8bitの場合は256通りで16bitの場合は65536通り
よって、配列Bにデータが書き込まれる可能性の有る部分は8bitの場合は256、16bitの場合65536
16ビットごとの場合の配列Bに対する書き込みのデタラメさは256倍酷い
またデータの書き込み位置を記録する為の配列も256倍大きいため、初期化する回数は8bitと比べて半分になっているが初期化に掛かるコストも大きい

8bit毎に処理する場合でも、ソートするデータが64bitと32bitの場合で比べると、配列のサイズが1MBを超えたあたりから遅くなっている
これはキャッシュの影響だと思われる

MSD方式の基数ソートは初期化の回数がとにかく多いため遅かった
初期化 → 上位16bitの処理 → 初期化 → 上位16bitがxxxxの場合の処理 → 初期化 → 上位16bitがyyyyの場合の処理 → …
ただし利点はある基数で配列を分類して更に分類されたその配列を分類するためメモリのアクセスの空間局所性は良い
分類した結果が1つだけ場合は明らかに追加で分類する必要が無いため処理を飛ばす
これの影響でグラフをみると変な挙動になっている

これらを検証する為に、MSD方式で配列を分類して、分類された配列が1MBを下回ったらそれをLSD方式をソート
ただし分類された配列がある程度小さいなら代わりにマージソートするハイブリッドを作った

512MBのソートでも性能があまり落ちない
ただし、実用性が無い。
与えられた配列の2倍のメモリを追加で確保している時点で問題がある。
そもそもギガバイト単位の巨大な配列のソートは需要が無いと思われる。

4バイトのデータをソートするなら要素数が128を超えたら基数ソート優位
8バイトの場合は256から優位になる

符号なし整数以外のデータを取り扱う場合に必要な工夫

固定長な符号なし整数へ変換して処理すれば良い。
この変換が兼ね備えているべき機能は、

  • 可逆であること
  • 変換前のデータ型での順序関係と変換後の符号なし整数での順序関係が同じであること

の2つ。
これさえできれば、基数ソートで処理できる様に変換して後で元に戻すだけ。

浮動小数点の場合は、
- 符号ビットが1の場合:全ビット反転
- 符号ビットが0の場合:符号ビットのみ反転
元に戻す際は、
- 符号ビットが1の場合:符号ビットのみ反転
- 符号ビットが0の場合:全ビット反転

NaNは後でIsNaNの結果を使ってソートすれば良い。

配列の長さと処理時間

32bit符号なし整数

plot1.png

64bit符号なし整数

plot2.png

float型

plot3.png

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1