-3

I'm getting weird timing results for quicksort with a first element pivot. My code runs quicksort first with an unsorted, non-randomized list of integers. It then sorts the previous sorted list. With my chosen pivot, I expected quicksort to run worst with a sorted list than an unsorted one. I've verified this with the following results:

Quicksort 1st run (unsorted, non-randomized)
# of calls to exch(): 14
# of calls to sort(): 17
# of calls to partition():8
# of calls to less(): 47
Sorted?: true
Time to sort input: 0.004212 secs

Quicksort 2nd run (sorted)
# of calls to exch(): 25
# of calls to sort(): 40
# of calls to partition(): 19
# of calls to less(): 138
Sorted?: true
Time to sort input: 0.000535 secs

Notice the timing doesn't make sense. Quicksort 2nd run should be slower than Quicksort 1st run.

Timing code:
start();
Quick.sort(a);
stop();

private static void start(){
   start = System.nanoTime();       
}

private static void stop(){
   elapsed = (System.nanoTime() - start)/1E9;
}   

The quicksort algorithm is correct. So there must be a problem with the way I'm implementing my timer.

Complete Code:

public class Quick {

static int exchCount; //DEBUG
static int sortCount; //DEBUG
static int partitionCount; //DEBUG
static int compareCount; //DEBUG

public static void sort(Comparable[]a){
    //StdRandom.shuffle(a);
    exchCount=0; //DEBUG
    sortCount=0; //DEBUG
    partitionCount=0; //DEBUG
    compareCount=0; //DEBUG
    sort(a, 0, a.length-1);
    System.out.printf("# of calls to exch(): %d%n", exchCount); //DEBUG
    System.out.printf("# of calls to sort(): %d%n", sortCount); // DEBUG
    System.out.printf("# of calls to partition(): %d%n", partitionCount); // DEBUG
    System.out.printf("# of calls to less(): %d%n", compareCount); // DEBUG
    return;

}

private static void sort(Comparable a[], int lo, int hi){
    sortCount++; // DEBUG

    if (hi<=lo) return; // base case    
    int p = partition(a, lo, hi); // select partition
    sort(a, lo, p-1); // recursively sort left side of partition
    sort(a, p+1, hi); // recursively sort right side of partition

    return; 
}


private static int partition(Comparable[]a, int lo, int hi){    
    partitionCount++; //DEBUG
    int i = lo, j = hi+1; // set pointers i (left) & j (right)

    Comparable p = a[lo]; // select a partition point we'll use lo as default

    while (true){

        // continue walking right if values are < p
        // captures any value < p
        while (less(a[++i], p)) if(i==hi)break;

        // continue walking left if values are > p
        while (less(p, a[--j]));

        if(i>=j) break; // has i crossed j?

        exch(a, i, j);

    }

    exch(a, lo, j); 
    return j;
}


private static boolean less(Comparable a, Comparable b){
    compareCount++; //DEBUG
    return a.compareTo(b)<0;

}

private static void exch(Comparable[] a, int i, int j){
    exchCount++; //DEBUG
    Comparable tmp = a[i];
    a[i] = a[j];
    a[j] = tmp; 

}

}


public class SortClient {

static double start=0, elapsed=0;
public static void main(String[] args) {

    for(int i=0; i<10; i++){

        //Comparable[] a={"K","R","A","T","E","L","E","P","U","I"
        //  ,"M","Q","C","X","O","S"};
        //Comparable[] a = DataReader.readInt("http://algs4.cs.princeton.edu/14analysis/8Kints.txt");
        Comparable[] a={8,4,45,23,13,1,65,44,9,8,3,33,21};

        start();
        Quick.sort(a);
        stop();
        System.out.printf("Quicksort#1%n");
        System.out.printf("Sorted?: %b%n",isSorted(a));
        System.out.printf("Time to sort input: %f secs%n%n%n",elapsed);

        start();
        Quick.sort(a);
        stop();
        System.out.printf("Quicksort#2%n");
        System.out.printf("Sorted?: %b%n",isSorted(a));
        System.out.printf("Time to sort input: %f secs%n%n%n",elapsed);

    }
}

private static void start(){

    start = System.nanoTime();

}

private static void stop(){

    elapsed = (System.nanoTime() - start)/1E9;


}

private static boolean isSorted(Comparable[]a){

    for(int i=0; i<a.length-2; i++)
       if(a[i].compareTo(a[i+1])>0)
           return false;
    return true;

}

}

Is there a quirk with Java's nanoTime() call I'm unaware of? Any ideas?

user1390616
  • 171
  • 1
  • 5
  • According to your output above, the second run is faster. – Oliver Charlesworth Jun 07 '14 at 15:43
  • 1
    how many times are you executing this? You must execute many thousands of times to get the jvm "warmed up". http://stackoverflow.com/questions/504103/how-do-i-write-a-correct-micro-benchmark-in-java – Brett Okken Jun 07 '14 at 15:44
  • @BrettOkken I tried executing 10, 100, 1000, 5000 times, and you're right on some, not all, the sorted(2nd run) does run slower than the unsorted(1st run) as expected. What do you mean by the jvm "warming up"? Is this Java/interpreted language specific problem? – user1390616 Jun 07 '14 at 16:13
  • After multiple executions the compiler can optimize execution behavior. You really need tens or hundreds of thousands of executions. – Brett Okken Jun 07 '14 at 16:14
  • @OliCharlesworth thank you for pointing that out. I've corrected my wording. – user1390616 Jun 07 '14 at 16:15
  • You'll need to provide a [complete code sample](http://stackoverflow.com/help/mcve), including code to generate test data and the actual function. – Bernhard Barker Jun 07 '14 at 16:46
  • Do you use the first sort output as input to the second sort? Do you do anything to use the result of the second sort? – Patricia Shanahan Jun 07 '14 at 16:51
  • I'm surprised no one pointed this out but your array size is only 13. For true comparison you should have an array size of atleast 1,000. – Abhishek Bansal Jun 07 '14 at 17:56
  • @PatriciaShanahan Yes first output is used as input to the second sort. Pls see the complete code. I added it per Dukeling's suggestion. – user1390616 Jun 07 '14 at 18:02
  • @AbhishekBansal I tried using a list of 1000. An unsorted list takes 0.005885 secs vs 0.005504 secs sorted, making the sorted list slight faster. However, subsequent runs after that are all behaving as expected with a difference of one significant figure. So, you are correct. However, why does the 1st iteration show a sorted list to be faster? – user1390616 Jun 07 '14 at 18:13
  • It takes the JVM a while to determine what to cache and what to optimize. The initial replications will be different from later ones while the JVM dials in towards optimal performance. – pjs Jun 07 '14 at 18:26
  • @AbhishekBansal I tried using a list of 4000 integers 10 times. The quicksort performs as expected, i.e.(Tsorted > Tunsorted) all 10 times. I have to wait 8 hrs to post an answer. Thanks! – user1390616 Jun 07 '14 at 18:42
  • @user1390616 I don't think it is relevant to this problem, but in general when benchmarking make sure each alternative is receiving data supplied in exactly the same way, and the results are definitely used, and used in the same way. That limits the compiler's ability to drop code related to [dead variables](http://en.wikipedia.org/wiki/Dead_code_elimination). – Patricia Shanahan Jun 08 '14 at 00:46

2 Answers2

2

"We all know quicksort runs worst with a sorted list than an unsorted one": this is an audacious statement.

The efficiency of Quicksort rests on the proper choice of the pivots: good pivots are such that they split the zone to be sorted in a balanced way, so that the process remains dichotomic, and this leads to an O(N.Lg(N)) behavior. On the opposite, poor choices causing maximum imbalance can lead to quadratic O(N²) degeneracy because the process remains incremental.

Naïve pivot selection strategies, such as "first element" will indeed cause the worst case to arise on sorted sequences. But wiser implementation, such as " median of three (first, middle and last)" will perform optimally.

  • Yves, you are right. I should have chosen my words more carefully. – user1390616 Jun 07 '14 at 19:18
  • From what I see, you are doing experiments on small arrays (at most a few tenth elements). For numerous reasons, you should work on much larger ones. –  Jun 07 '14 at 19:27
  • I ran larger arrays per Abhishek Bansal's suggestion with 1k and 4k elements. And the presorted arrays performed worst than the unsorted array. – user1390616 Jun 07 '14 at 19:33
0

As @AbhisekBansal pointed out, quicksort runs as expected over large arrays, i.e >1000 items. After running quicksort over a list of 4K integers, quicksort performed as expected in all iterations (i.e, T*unsorted* < T*sorted*):

Output (first 4 iterations):

Quicksort#1(unsorted)
calls to exch(): 11466
calls to sort(): 5329
calls to partition(): 2664
calls to less(): 60092
Sorted?: true
Time to sort input: 0.006784 secs

Quicksort#2(presorted)
calls to exch(): 3999
calls to sort(): 7999
calls to partition(): 3999
calls to less(): 8005998
Sorted?: true
Time to sort input: 0.079226 secs

Quicksort#1(unsorted)
calls to exch(): 11466
calls to sort(): 5329
calls to partition(): 2664
calls to less(): 60092
Sorted?: true
Time to sort input: 0.001649 secs

Quicksort#2(presorted)
calls to exch(): 3999
calls to sort(): 7999
calls to partition(): 3999
calls to less(): 8005998
Sorted?: true
Time to sort input: 0.079353 secs

Quicksort#1(unsorted)
calls to exch(): 11466
calls to sort(): 5329
calls to partition(): 2664
calls to less(): 60092
Sorted?: true
Time to sort input: 0.001680 secs

Quicksort#2(presorted)
calls to exch(): 3999
calls to sort(): 7999
calls to partition(): 3999
calls to less(): 8005998
Sorted?: true
Time to sort input: 0.079331 secs

Quicksort#1(unsorted)
calls to exch(): 11466
calls to sort(): 5329
calls to partition(): 2664
calls to less(): 60092
Sorted?: true
Time to sort input: 0.001625 secs

Quicksort#2(presorted)
calls to exch(): 3999
calls to sort(): 7999
calls to partition(): 3999
calls to less(): 8005998
Sorted?: true
Time to sort input: 0.079593 secs
user1390616
  • 171
  • 1
  • 5