2011-12-03 31 views
5

我写了一些java类,来评估/演示不同的排序算法。不过,当我运行我的演示课时,我感到困惑。希望你们能给我一个解释。 (这个问题不是作业。)相同的代码,相同的输入,有时运行速度快,有时慢,为什么?

首先我会列出一些与此问题相关的代码。

AbstractDemo

public abstract class AbstractDemo { 
    protected final int BIG_ARRAY_SIZE = 20000; 
    protected final int SMALL_ARRAY_SIZE = 14; 
    protected Stopwatch stopwatch = new Stopwatch(); 

    public final void doDemo() { 
     prepareDemo(); 
     specificDemo(); 
    } 

    protected abstract void prepareDemo(); 

    protected abstract void specificDemo(); 

    protected final void printInfo(final String text) { 
     System.out.println(text); 
    } 
} 

SortingDemo

public class SortingDemo extends AbstractDemo { 
    private static final String FMT = "%-10s| %-21s| %7s ms."; 
    private static final String SPL = AlgUtil.lineSeparator('-', 45); 
    private static final String SPLT = AlgUtil.lineSeparator('=', 45); 

    private int[] data; 

    private final List<Sorting> demoList = new LinkedList<Sorting>(); 

    @Override 
    protected void specificDemo() { 
     int[] testData; 
     //*** this comment is interesting!!! for (int x = 1; x < 6; x++) { 

      printInfo(String.format("Sorting %7s elements", data.length)); 
      printInfo(SPLT); 
      for (final Sorting sort : demoList) { 

       // here I made a copy of the original Array, avoid to sort an already sorted array. 
       testData = new int[data.length]; 
       System.arraycopy(data, 0, testData, 0, data.length); 
       stopwatch.start(); 
       // sort 
       sort.sort(testData); 
       stopwatch.stop(); 
       printInfo(String.format(FMT, sort.getBigO(), sort.getClass().getSimpleName(), stopwatch.read())); 
       printInfo(SPL); 
       testData = null; 
       stopwatch.reset(); 
      } 
     //} 
    } 

    @Override 
    protected void prepareDemo() { 
     data = AlgUtil.getRandomIntArray(BIG_ARRAY_SIZE, BIG_ARRAY_SIZE * 5, false); 
     demoList.add(new InsertionSort()); 
     demoList.add(new SelectionSort()); 
     demoList.add(new BubbleSort()); 
     demoList.add(new MergeSort()); //here is interesting too 
     demoList.add(new OptimizedMergeSort()); 

    } 

    public static void main(final String[] args) { 
     final AbstractDemo sortingDemo = new SortingDemo(); 
     sortingDemo.doDemo(); 
    } 
} 

秒表

public class Stopwatch { 
    private boolean running; 
    private long startTime; 
    private long elapsedMillisec; 

    public void start() { 
     if (!running) { 
      this.startTime = System.currentTimeMillis(); 
      running = true; 
     } else { 
      throw new IllegalStateException("the stopwatch is already running"); 
     } 
    } 

    public void stop() { 
     if (running) { 
      elapsedMillisec = System.currentTimeMillis() - startTime; 
      running = false; 
     } else { 
      throw new IllegalStateException("the stopwatch is not running"); 
     } 
    } 

    public void reset() { 
     elapsedMillisec = 0; 

    } 

    public long read() { 
     if (running) { 
      elapsedMillisec = System.currentTimeMillis() - startTime; 
     } 
     return this.elapsedMillisec; 
    } 

} 

方法生成随机阵列

public static int[] getRandomIntArray(final int len, final int max, boolean allowNegative) { 
     final int[] intArray = new int[len]; 
     final Random rand = new Random(); 
     rand.setSeed(20100102); 

     if (!allowNegative) { 
      if (max <= 0) { 
       throw new IllegalArgumentException("max must be possitive if allowNegative false"); 
      } 
      for (int i = 0; i < intArray.length; i++) { 
       intArray[i] = rand.nextInt(max); 
      } 
     } else { 
      int n; 
      int i = 0; 
      while (i < len) { 
       n = rand.nextInt(); 
       if (n < max) { 
        intArray[i] = n; 
        i++; 
       } 
      } 
     } 

     return intArray; 
    } 

你可以看到,我生成一个int数组,具有20000层的元件。由于我在getRandomIntArray方法中有一个固定的种子,每次我调用它时,我总是有相同的数组。类SortingDemo有主要方法,如果我运行这个类,我得到了输出:

Sorting 20000 elements 
============================================= 
O(n^2) | InsertionSort  |  101 ms. 
--------------------------------------------- 
O(n^2) | SelectionSort  |  667 ms. 
--------------------------------------------- 
O(n^2) | BubbleSort   | 1320 ms. 
--------------------------------------------- 
O(nlog(n))| MergeSort   |  39 ms. 
--------------------------------------------- 
O(?)  | OptimizedMergeSort |  11 ms. 
--------------------------------------------- 

看起来没问题。现在有些事情让我感到困惑。如果我更改了SortingDemo的demoList.add()序列,说:

demoList.add(new InsertionSort()); 
    demoList.add(new SelectionSort()); 
    demoList.add(new BubbleSort()); 
    // OptimizedMergeSort before Mergesort 
    demoList.add(new OptimizedMergeSort()); 
    demoList.add(new MergeSort()); 

我:

Sorting 20000 elements 
============================================= 
O(n^2) | InsertionSort  |  103 ms. 
--------------------------------------------- 
O(n^2) | SelectionSort  |  676 ms. 
--------------------------------------------- 
O(n^2) | BubbleSort   | 1313 ms. 
--------------------------------------------- 
O(?)  | OptimizedMergeSort |  41 ms. 
--------------------------------------------- 
O(nlog(n))| MergeSort   |  14 ms. 
--------------------------------------------- 

为什么输出是从第一次运行有什么不同? OptimizedMergeSort花的时间比正常归并...

如果我取消注释SortingDemo的for (int x=1; x<6; x++)线(与同一阵列运行测试5倍)我:

Sorting 20000 elements 
============================================= 
O(n^2) | InsertionSort  |  101 ms. 
--------------------------------------------- 
O(n^2) | SelectionSort  |  668 ms. 
--------------------------------------------- 
O(n^2) | BubbleSort   | 1311 ms. 
--------------------------------------------- 
O(?)  | OptimizedMergeSort |  37 ms. 
--------------------------------------------- 
O(nlog(n))| MergeSort   |  10 ms. 
--------------------------------------------- 

Sorting 20000 elements 
============================================= 
O(n^2) | InsertionSort  |  94 ms. 
--------------------------------------------- 
O(n^2) | SelectionSort  |  665 ms. 
--------------------------------------------- 
O(n^2) | BubbleSort   | 1308 ms. 
--------------------------------------------- 
O(?)  | OptimizedMergeSort |  5 ms. 
--------------------------------------------- 
O(nlog(n))| MergeSort   |  7 ms. 
--------------------------------------------- 

Sorting 20000 elements 
============================================= 
O(n^2) | InsertionSort  |  116 ms. 
--------------------------------------------- 
O(n^2) | SelectionSort  |  318 ms. 
--------------------------------------------- 
O(n^2) | BubbleSort   |  969 ms. 
--------------------------------------------- 
O(?)  | OptimizedMergeSort |  5 ms. 
--------------------------------------------- 
O(nlog(n))| MergeSort   |  10 ms. 
--------------------------------------------- 

Sorting 20000 elements 
============================================= 
O(n^2) | InsertionSort  |  116 ms. 
--------------------------------------------- 
O(n^2) | SelectionSort  |  319 ms. 
--------------------------------------------- 
O(n^2) | BubbleSort   |  964 ms. 
--------------------------------------------- 
O(?)  | OptimizedMergeSort |  5 ms. 
--------------------------------------------- 
O(nlog(n))| MergeSort   |  5 ms. 
--------------------------------------------- 

Sorting 20000 elements 
============================================= 
O(n^2) | InsertionSort  |  116 ms. 
--------------------------------------------- 
O(n^2) | SelectionSort  |  320 ms. 
--------------------------------------------- 
O(n^2) | BubbleSort   |  963 ms. 
--------------------------------------------- 
O(?)  | OptimizedMergeSort |  4 ms. 
--------------------------------------------- 
O(nlog(n))| MergeSort   |  6 ms. 
--------------------------------------------- 

对于其他的分类法,中,结果如下合理。但对于mergeSort,为什么第一次运行需要比以后更长的时间? OptimizedMergeSort为37ms:4ms。

我认为即使Optimized/MergeSort的实现是错误的,输出也应该保持不变,为什么有时候同样的方法调用需要更长的时间,有时候更短的时间?

作为信息,所有那些* Sort类都扩展了超级抽象类Sorting。它有一个抽象方法void sort(int[] data)

MergeSort有mergeSorting方法和merge()方法。 OptimizedMergeSort扩展MergeSort,并覆盖mergeSorting()方法,(因为当数组大小< = 7时,它将执行insertSort)并重用MergeSort类中的merge()方法。

感谢您阅读这篇长文和代码。我很感激你们是否可以给我一些解释。

所有的测试都是在Eclipse下在linux下完成的。

+0

做归并和OptimizedMergeSort共享代码?如果是这样,那么很可能你会看到jit编译的影响。在java中编写准确的微基准测试是非常困难的(由于jit),并且有许多文章专门讨论这个主题。 – jtahlborn

+0

@minitech thx评论。我认为随机不会影响结果。因为prepareDemo只被调用一次。无论它是什么样的随机数,它都是相同的数组。 – Kent

+0

@jtahlborn是的,我在写的问题,OptimzedMS是MS的一个子类,它们共享合并方法(在MS类保护)。感谢您的JIT暗示,我将搜索和阅读它 – Kent

回答

3

微基准测试Java代码是出了名的棘手。

即时编译器很有可能会在某个时刻将Java字节码编译为本机机器码。每个编译都需要时间,但生成的代码可能运行得更快。

还有其他缺陷,但我认为上面是最有可能在你的情况。

以下SO答案是一个非常良好的阅读:https://stackoverflow.com/a/513259/367273

+0

这是我第一次遇到jit问题。我会先做一些阅读。你的链接会有帮助。感谢您向我展示了方向。 – Kent

1

基本上,有两种可能的原因,都与环境有关,而不是与程序有关:其他的东西占用了大量的空间,或者其他的东西占用了大量的CPU时间。

在第一种情况下,您还有其他一些耗费物理内存的程序,导致程序更频繁地进行页面和/或垃圾收集。

在第二个,其他一些程序(如果你运行Mac,我敢打赌时间机器)间歇地运行消耗CPU。

无论如何,你可以找到的方法是开始使用Java附带的工具。 jconsole和VirtualVM是很好的可能性。

一般来说,当您遇到性能问题时,需要采取三个关键步骤:测量,测量和MEASURE。

更新

我同意,JIT可以有所作为,但我得到的印象是,这是独立运行;你每次都调用一个新的JVM。这最大限度地减少了JIT的影响。

+0

我同意查理马丁。而且,两次运行之间的时间差异非常小。这根本不奇怪。 – Boundless

+0

每组运行都在同一个jvm中。很清楚的是jit。 – jtahlborn

+0

不,@ jtahlborn,它*不*清楚地说明JIT - 它*可能*或*振振有词* JIT。将JIT从各种其他影响中分离出来是没有实验基础的。地狱,即使它运行在一个JVM实例中,JIT也不是JVM中唯一的潜在影响。正如我所说,“措施!”。业余爱好者只是断言性能问题是什么;专业人员测量 –

0

为了减少JIT的效果,你可以放弃最初的几个运行每个测试。也可以获得平均超过100或1000次运行的更准确的结果。

在开始测量之前,您还可以执行System.gc()和Thread.yeild()。 如果你要测试只有少数迭代喜欢system.nanoTime()和不使用System.currentTimeMillis的(它不够准确)。

+0

thx您的建议,我会试试看。 – Kent

相关问题