您当前采用的方法似乎存在一些系统性错误。我将说明您面临的一些最明显的实验问题,即使它们可能不是直接导致您实验结果的罪魁祸首。
JVM编译
正如我之前在评论中所述,JVM 默认情况下会以解释模式运行您的代码。这意味着在您的方法中找到的每个字节码指令都将被当场解释,然后运行。
这种方法的优点是,与在每次运行启动时编译为本机代码的 Java 程序相比,它允许您的应用程序具有更快的启动时间。
缺点是,虽然不会影响启动性能,但您会得到一个比其他方式执行速度更慢的程序。
为了缓解这两个问题,JVM 团队进行了权衡。最初,您的程序将被解释,但 JVM 将收集有关哪些方法(或方法的一部分)正在被密集使用的信息,并且只会编译那些似乎经常使用的方法。编译时,性能会受到一点影响,但代码会比以前快得多。
进行测量时必须考虑这一事实。
标准方法是“预热 JVM”,即运行算法一段时间以确保 JVM 完成它需要执行的所有编译。重要的是让 JVM 预热的代码与您想要进行基准测试的代码相同,否则在对代码进行基准测试时可能会进行一些编译。
测量时间
测量时间时,您应该使用System.nanoTime()
代替System.currentTimeMillis
。我不会详细介绍这些细节,这些可以访问here https://stackoverflow.com/questions/351565/system-currenttimemillis-vs-system-nanotime.
你也应该小心。以下代码块may乍一看似乎是相同的,但大多数时候会产生不同的结果:
totalDuration = 0;
for (i = 0; i < 1000; ++i)
startMeasure = now();
algorithm();
endMeasure = now();
duration = endMeasure - startMeasure;
totalDuration += duration;
}
//...
TRIALS_COUNT = 1000;
startMeasure = now();
for (i = 0; i < TRIALS_COUNT; ++i)
algorithm();
}
endMeasure = now();
duration = endMeasure - startMeasure / TRIALS_COUNT;
为什么?因为特别是为了更快algorithm()
实施速度越快,获得准确结果的难度就越大。
输入值大
渐近符号对于理解不同的算法如何针对较大的值进行升级很有用n
。将它们应用于小输入值通常是无意义的,因为在这种情况下,通常您想要的是计算精确的操作数量及其相关成本(类似于 Jakub 所做的事情)。大多数时候,大 O 表示法仅对大 O 有意义。 Big O 会告诉您该算法如何处理难以忍受的输入值大小,而不是它如何处理较小的数字。典型的例子是快速排序,对于大数组来说它是王者,但对于大小为 4 或 5 的数组来说通常会比选择排序或插入排序慢。不过,您的输入大小似乎足够大。
最后一点
正如 Chang 先前所说,Jakub 所做的数学练习是完全错误的,不应被考虑。