JavaのTimSortがバグってる件について
Python で実装され、その後 Java にも移植されたソートアルゴリズムである TimSort が盛大にバグっていることが発見されました。
このバグがどのようにして発生するのかについては、以下のドキュメントを精査して下さい。
- TimSort fails with ArrayIndexOutOfBoundsException on worst case long arrays
- OpenJDK’s java.utils.Collection.sort() is broken: The good, the bad and the worst case
どんなことが起こるのか
通常の利用では想定しえない場所でArrayIndexOutOfBoundsException
が発生します。
例えば、以下のようなスタックトレースになります。
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 40
at java.util.TimSort.pushRun(Unknown Source)
at java.util.TimSort.sort(Unknown Source)
at java.util.Arrays.sort(Unknown Source)
at TestTimSort.main(TestTimSort.java:18)
発生する条件
以下の条件を全て満たすと発生します。
- Android 及び Java7 以降の Java で発生する
- Java に TimSort が標準で組み込まれたのが Java8 です
- Android は標準のソートアルゴリズムが TimSort です
java.util.Arrays#sort
を呼出すと発生する。例えば、以下の API も間接的に当該 API を呼出す- java.util.List#sort
- java.util.Collections#sort
- 要素数が
67108864
件以上で発生する- もっと少ない件数で発生するケースがあるかもしれません。
- 太一がアルゴリズムを厳密に理解できてないので件数については要注意です。より少ない件数で発生するかもしれません。
- TimSort にとって都合の悪い並び順だと発生する
- 要素数が多くても TimSort にとって都合の悪い並び順でなければ発生しない
尚、プリミティブ型の配列を対象としたソートでは DualPivotQuicksort が使われるため、今回の問題は発生しません。
対応策
Android の場合
ワークアラウンドはありません。
Java の場合
java.util.Arrays.useLegacyMergeSort
を設定する。
この設定をおこなうとソートのアルゴリズムが古くなるのでシステムのパフォーマンスが劣化する可能性があります。
JVM の起動オプションで設定する
以下のように-D オプションで JVM にオプションを渡します。
java -Djava.util.Arrays.useLegacyMergeSort=true