[英]Jvm native code compilation crazyness - I seem to suffer odd performance penalties for some time even after the code is compiled. Why?
在 Java 中对简单的 QuickSort 实现进行基准测试时,我在绘制的n vs time
图形中遇到了意想不到的颠簸:
我知道 HotSpot 会在某些方法似乎被大量使用后尝试将代码编译为本机代码,因此我使用-XX:+PrintCompilation
运行了 JVM。 经过反复试验,似乎总是以相同的方式编译算法的方法:
@ iteration 6 -> sorting.QuickSort::swap (15 bytes)
@ iteration 7 -> sorting.QuickSort::partition (66 bytes)
@ iteration 7 -> sorting.QuickSort::quickSort (29 bytes)
我用这个添加的信息重复上面的图形,只是为了让事情更清楚一点:
在这一点上,我们都必须问自己:为什么在代码编译后我们仍然得到那些丑陋的驼峰? 可能跟算法本身有关系? 肯定可以,幸运的是我们有一种快速的方法来解决这个问题,使用-XX:CompileThreshold=0
:
无赖。 这真的一定是 JVM 在后台做的事情吗? 但是,我推测尽管正在编译代码。 实际开始使用编译后的代码可能需要一段时间。 也许在这里和那里添加几个Thread.sleep()
可以帮助我们解决这个问题?
哎哟,绿色的 function 是 QuickSort 的代码,每次运行之间有 1000 毫秒的内部间隔(附录中有详细信息)。 而蓝色的 function 是我们的旧款(仅供比较)。
乍一看,给 HotSpot 时间似乎只会让事情变得更糟! 也许它只是因为某些其他因素而变得更糟,例如缓存问题?
免责声明:我正在为所示图形的每个点运行 1000 次试验,并使用System.nanoTime()
来测量结果。
在这个阶段,你们中的一些人可能想知道使用sleep()
会如何扭曲结果。 我再次运行 Red Plot(没有本机编译),现在中间有睡眠:
可怕的!
在这里,我展示了我正在使用的QuickSort
代码,以防万一:
public class QuickSort {
public <T extends Comparable<T>> void sort(int[] table) {
quickSort(table, 0, table.length - 1);
}
private static <T extends Comparable<T>> void quickSort(int[] table,
int first, int last) {
if (first < last) { // There is data to be sorted.
// Partition the table.
int pivotIndex = partition(table, first, last);
// Sort the left half.
quickSort(table, first, pivotIndex - 1);
// Sort the right half.
quickSort(table, pivotIndex + 1, last);
}
}
/**
* @author http://en.wikipedia.org/wiki/Quick_Sort
*/
private static <T extends Comparable<T>> int partition(int[] table,
int first, int last) {
int pivotIndex = (first + last) / 2;
int pivotValue = table[pivotIndex];
swap(table, pivotIndex, last);
int storeIndex = first;
for (int i = first; i < last; i++) {
if (table[i]-(pivotValue) <= 0) {
swap(table, i, storeIndex);
storeIndex++;
}
}
swap(table, storeIndex, last);
return storeIndex;
}
private static <T> void swap(int[] a, int i, int j) {
int h = a[i];
a[i] = a[j];
a[j] = h;
}
}
以及我用来运行基准测试的代码:
public static void main(String[] args) throws InterruptedException, IOException {
QuickSort quickSort = new QuickSort();
int TRIALS = 1000;
File file = new File(Long.toString(System.currentTimeMillis()));
System.out.println("Saving @ \"" + file.getAbsolutePath() + "\"");
for (int x = 0; x < 30; ++x) {
// if (x > 4 && x < 17)
// Thread.sleep(1000);
int[] values = new int[x];
long start = System.nanoTime();
for (int i = 0; i < TRIALS; ++i)
quickSort.sort(values);
double duration = (System.nanoTime() - start) / TRIALS;
String line = x + "\t" + duration;
System.out.println(line);
FileUtils.writeStringToFile(file, line + "\r\n", true);
}
}
好吧,看来我自己解决了这个问题。
编译后的代码可能需要一段时间才能生效,我的想法是正确的。问题是我实际实现基准测试代码的方式存在缺陷:
if (x > 4 && x < 17)
Thread.sleep(1000);
在这里我假设唯一的“受影响”区域在 4 到 17 之间,我可以打开 go 并在这些值上睡一觉。 根本不是这样。 以下 plot 可能具有启发性:
在这里,我将原始无编译 function(红色)与另一个无编译 function 进行比较,但中间有睡眠。 正如您所看到的,它们以不同的数量级工作,这意味着混合使用和不使用睡眠的代码结果将产生不合理的结果,正如我所做的那样。
最初的问题仍未得到解答。 是什么导致即使在编译发生后也会出现驼峰? 让我们尝试找出答案,在所有得分中睡眠 1 秒:
这会产生预期的结果。 奇怪的驼峰正在发生,本机代码仍然没有启动。
比较睡眠 50 毫秒和睡眠 1000 毫秒 function 再次产生预期结果:
(灰色的好像还是有点延迟)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.