• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.
8  *
9  * This code is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * version 2 for more details (a copy is included in the LICENSE file that
13  * accompanied this code).
14  *
15  * You should have received a copy of the GNU General Public License version
16  * 2 along with this work; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  *
19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20  * or visit www.oracle.com if you need additional information or have any
21  * questions.
22  */
23 
24 /*
25  * @test
26  * @bug 8072909
27  * @summary Test TimSort stack size on big arrays
28  * @library /test/lib
29  * @modules java.management
30  * @requires (vm.debug == false)
31  * @build TimSortStackSize2
32  * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
33  * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions
34  *     -XX:+WhiteBoxAPI TimSortStackSize2
35  */
36 package test.java.util.Arrays;
37 
38 import java.util.ArrayList;
39 import java.util.Arrays;
40 import java.util.List;
41 import java.util.function.Consumer;
42 
43 // import jdk.test.lib.process.OutputAnalyzer;
44 // import jdk.test.lib.process.ProcessTools;
45 // import jdk.test.whitebox.WhiteBox;
46 
47 // Android-changed: test relies on jdk.test.lib.process.
48 public class TimSortStackSize2 {
49 /*
50     public static void main(String[] args) {
51         if ( args == null || args.length == 0 ){
52             startMeWithArgs();
53         } else {
54             doTestOfTwoTimSorts(Integer.parseInt(args[0]));
55         }
56     }
57 
58     private static void startMeWithArgs(){
59         /*
60          * big tests not for regular execution on all platforms:
61          * run main/othervm -Xmx8g TimSortStackSize2 1073741824
62          * run main/othervm -Xmx16g TimSortStackSize2 2147483644
63          *
64         try {
65             Boolean compressedOops = WhiteBox.getWhiteBox()
66                                              .getBooleanVMFlag("UseCompressedOops");
67             long memory = (compressedOops == null || compressedOops) ? 385 : 770;
68             final String xmsValue = "-Xms" +     memory + "m";
69             final String xmxValue = "-Xmx" + 2 * memory + "m";
70 
71             System.out.printf("compressedOops: %s; Test will be started with \"%s %s\"%n",
72                               compressedOops, xmsValue, xmxValue);
73             OutputAnalyzer output = ProcessTools.executeTestJava(xmsValue,
74                                                                  xmxValue,
75                                                                  "TimSortStackSize2",
76                                                                  "67108864");
77             System.out.println(output.getOutput());
78             output.shouldHaveExitValue(0);
79         } catch (Exception e) {
80             e.printStackTrace();
81             throw new RuntimeException(e);
82         }
83     }
84 
85     private static void doTestOfTwoTimSorts(final int lengthOfTest){
86         boolean passed = doTest("TimSort", lengthOfTest,
87             (Integer [] a) -> Arrays.sort(a));
88         passed = doTest("ComparableTimSort", lengthOfTest, (Integer [] a) ->
89             Arrays.sort(a, (Object first, Object second) -> {
90                 return ((Comparable<Object>)first).compareTo(second);
91             }))
92             && passed;
93         if ( !passed ){
94             throw new RuntimeException();
95         }
96     }
97 
98     private static boolean doTest(final String msg, final int lengthOfTest,
99                                   final  Consumer<Integer[]> c){
100         Integer [] a = null;
101         try {
102             a = new TimSortStackSize2(lengthOfTest).createArray();
103             long begin = System.nanoTime();
104             c.accept(a);
105             long end = System.nanoTime();
106             System.out.println(msg + " OK. Time: " + (end - begin) + "ns");
107         } catch (ArrayIndexOutOfBoundsException e){
108             System.out.println(msg + " broken:");
109             e.printStackTrace();
110             return false;
111         } finally {
112             a = null;
113         }
114         return true;
115     }
116 
117     private static final int MIN_MERGE = 32;
118     private final int minRun;
119     private final int length;
120     private final List<Long> runs = new ArrayList<Long>();
121 
122     public TimSortStackSize2(final int len) {
123         this.length = len;
124         minRun = minRunLength(len);
125         fillRunsJDKWorstCase();
126     }
127 
128     private static int minRunLength(int n) {
129         assert n >= 0;
130         int r = 0;      // Becomes 1 if any 1 bits are shifted off
131         while (n >= MIN_MERGE) {
132             r |= (n & 1);
133             n >>= 1;
134         }
135         return n + r;
136     }
137 
138     /**
139      * Adds a sequence x_1, ..., x_n of run lengths to <code>runs</code> such that:<br>
140      * 1. X = x_1 + ... + x_n <br>
141      * 2. x_j >= minRun for all j <br>
142      * 3. x_1 + ... + x_{j-2}  <  x_j  <  x_1 + ... + x_{j-1} for all j <br>
143      * These conditions guarantee that TimSort merges all x_j's one by one
144      * (resulting in X) using only merges on the second-to-last element.
145      * @param X  The sum of the sequence that should be added to runs.
146      *
147     private void generateJDKWrongElem(long X) {
148         for(long newTotal; X >= 2 * minRun + 1; X = newTotal) {
149             //Default strategy
150             newTotal = X / 2 + 1;
151             //Specialized strategies
152             if(3 * minRun + 3 <= X && X <= 4*minRun+1) {
153                 // add x_1=MIN+1, x_2=MIN, x_3=X-newTotal  to runs
154                 newTotal = 2 * minRun + 1;
155             } else if (5 * minRun + 5 <= X && X <= 6 * minRun + 5) {
156                 // add x_1=MIN+1, x_2=MIN, x_3=MIN+2, x_4=X-newTotal  to runs
157                 newTotal = 3 * minRun + 3;
158             } else if (8 * minRun + 9 <= X && X <= 10 * minRun + 9) {
159                 // add x_1=MIN+1, x_2=MIN, x_3=MIN+2, x_4=2MIN+2, x_5=X-newTotal  to runs
160                 newTotal = 5 * minRun + 5;
161             } else if (13 * minRun + 15 <= X && X <= 16 * minRun + 17) {
162                 // add x_1=MIN+1, x_2=MIN, x_3=MIN+2, x_4=2MIN+2, x_5=3MIN+4, x_6=X-newTotal  to runs
163                 newTotal = 8 * minRun + 9;
164             }
165             runs.add(0, X - newTotal);
166         }
167         runs.add(0, X);
168     }
169 
170     /**
171      * Fills <code>runs</code> with a sequence of run lengths of the form<br>
172      * Y_n     x_{n,1}   x_{n,2}   ... x_{n,l_n} <br>
173      * Y_{n-1} x_{n-1,1} x_{n-1,2} ... x_{n-1,l_{n-1}} <br>
174      * ... <br>
175      * Y_1     x_{1,1}   x_{1,2}   ... x_{1,l_1}<br>
176      * The Y_i's are chosen to satisfy the invariant throughout execution,
177      * but the x_{i,j}'s are merged (by <code>TimSort.mergeCollapse</code>)
178      * into an X_i that violates the invariant.
179      * X is the sum of all run lengths that will be added to <code>runs</code>.
180      *
181     private void fillRunsJDKWorstCase() {
182         long runningTotal = 0;
183         long Y = minRun + 4;
184         long X = minRun;
185 
186         while (runningTotal + Y + X <= length) {
187             runningTotal += X + Y;
188             generateJDKWrongElem(X);
189             runs.add(0, Y);
190 
191             // X_{i+1} = Y_i + x_{i,1} + 1, since runs.get(1) = x_{i,1}
192             X = Y + runs.get(1) + 1;
193 
194             // Y_{i+1} = X_{i+1} + Y_i + 1
195             Y += X + 1;
196         }
197 
198         if (runningTotal + X <= length) {
199             runningTotal += X;
200             generateJDKWrongElem(X);
201         }
202 
203         runs.add(length - runningTotal);
204     }
205 
206     private Integer [] createArray() {
207         Integer [] a = new Integer[length];
208         Arrays.fill(a, 0);
209         int endRun = -1;
210         for (long len : runs) {
211             a[endRun += len] = 1;
212         }
213         a[length - 1] = 0;
214         return a;
215     }
216 */
217 }
218