在使用手动插入/选择/基数排序对int数组进行排序时,Java的执行速度会比C快,这有什么原因吗



平台:OpenBSD,编译器:gcc,javac(OpenJDK版本17)

我已经用各种语言做了一些排序基准测试,Java程序相对于C程序的性能让我相当惊讶。

我用两种语言编写了完全相同的排序算法,Java程序的完成速度几乎是C的两倍,除了Java之外,所有其他语言都比C实现慢。

基准测试包括在一个随机的数字数组上运行排序算法一定次数。

我正在用-O3-Ofast编译程序,所以我不能再应用任何编译器优化。

确切的代码可以在这里找到,但这里有一段摘录:

Java:

public static void benchmark(SortingFunction func, int arraySize, int numTimes, String name, BufferedOutputStream bo) throws IOException {
int[][] arrs = new int[numTimes][arraySize];
for (int i = 0; i < numTimes; i ++) {
arrs[i] = genRandArray(arraySize);
}
long start = System.nanoTime();
for (int i = 0; i < numTimes; i ++) {
func.sort(arrs[i]);
}
long end = System.nanoTime();
double time = (double)(end - start) / 1e9;
System.out.println("It took " + time + " seconds to do " + name + " sort " +
numTimes + " times on arrays of size " + arraySize
);
String out = name+","+numTimes+","+arraySize+","+time;
for (char c : out.toCharArray()) {
bo.write(c);
}
bo.write('n');
}
public static void insertionSort(int[] array) {
for (int i = 1; i < array.length; i ++) {
int temp = array[i];
int j;
for (j = i - 1; j >= 0 && array[j] > temp; j --) {
array[j+1] = array[j];
}
array[j+1] = temp;
}
}

C:

void benchmark(void (*f)(int *, int), int arr_size, int num_times, char *name,
FILE *fp) {
int *arrs[num_times];
struct timeval start, end;
double t;
for (int i = 0; i < num_times; i++) {
arrs[i] = gen_rand_arr(arr_size);
}
gettimeofday(&start, NULL);
for (int i = 0; i < num_times; i++) {
f(arrs[i], arr_size);
}
gettimeofday(&end, NULL);
for (int i = 0; i < num_times; i++) {
free(arrs[i]);
}
t = ((double)(end.tv_sec * 1000000 + end.tv_usec -
(start.tv_sec * 1000000 + start.tv_usec))) /
1000000;
printf("It took %f seconds to do %s sort %d times on arrays of size %dn", t,
name, num_times, arr_size);
if (fp != NULL) {
fprintf(fp, "%s,%d,%d,%fn", name, num_times, arr_size, t);
}
}
void insertion_sort(int *arr, int arr_size) {
for (int i = 1; i < arr_size; i++) {
int temp = arr[i];
int j;
for (j = i - 1; j >= 0 && *(arr + j) > temp; j--) {
arr[j + 1] = arr[j];
}
arr[j + 1] = temp;
}
return;
}

Java是否正在进行一些优化以加快运行速度,从而以某种方式改变算法?这是怎么回事?

如有任何解释,不胜感激。

以下是可能有助于解释差异的结果表:

Java:

大小<1.033>15.677//tr><1000><1000><1000>11波哥大
名称代表时间
插入10001200
插入1000
插入100088.190
选择100012003.18
选择1000500048.377
选择1000268.608
基数1000
基数100050001.491
基数10003.563
波哥大11.330
波哥大113122.77

TL;DR

一般来说,像这样的简短片段不是比较语言的公平方式。有很多因素在起作用。当用C而不是Java编写代码时,代码不会自动变得更快。如果是这样的话,您可以编写一个Java2C转换器。编译器标志很重要,但也关系到程序员的技能。

更长的解释

我不能肯定,但这个:

for (j = i - 1; j >= 0 && arr[j] > temp; j--) {
arr[j + 1] = arr[j];
}

对缓存不是很友好,因为您正在向后遍历列表。我会尝试改变循环,使外循环进行向后遍历,而不是内循环。

但我想说,你的问题从根本上是有缺陷的。代码不会因为从Java重写到C而自动提高性能。同样,C程序也不会因为重写到汇编而自动提高速度。可以说,C允许编写比Java更快的程序,但最终,结果取决于程序员。

JIT编译器可以加快Java程序的速度,它可以在运行时根据当时的特定条件进行统计以优化代码。虽然可以使C编译器针对典型的工作负载进行优化,但它不能针对current的工作负载进行最优化。

您说过您使用-O3作为C代码。但是你用了什么靶子?你是针对你的机器还是针对普通机器进行了优化?JIT编译器知道要优化的目标。尝试使用-march=native

您确定int使用相同的尺寸吗?它在Java中是32位,但在C中可能是64位。如果您改用int32_t,它可能会加快C代码的速度。但它也可能减缓速度。(这不太可能是原因,但我只是想把它作为一种可能性)

当涉及到非常低级别的东西时,C通常会发光。

如果我们查看您的Java代码:

for (int i = 1; i < array.length; i ++) {
int temp = array[i];

在这个例子中,智能编译器可以很容易地看到array永远不会被越界访问。但如果我们有这样的东西呢:

while(<condition>) {
int temp = array[foo()];

其中不能预先确定CCD_ 8不会出界?然后,Java被迫进行持续的边界检查,以便能够抛出异常。该代码将被翻译成类似于:

while(<condition>) {
int i = foo();
if(i >= array.length)
throw exception;
int temp = array[i];

这提供了安全性,但降低了性能。C只允许您访问越界,这样更快,但可能会导致难以找到的错误。

我发现了一个有更多信息的好问题:为什么Java有可能比C++更快?

除此之外,我可以看到您在基准测试中包含了数据生成。这太糟糕了。在启动计时器之前生成数据。像这样:

int *arrs[num_times];
for (int i = 0; i < num_times; i++) 
arrs[i] = gen_rand_arr(arr_size);
gettimeofday(&start, NULL);
for (int i = 0; i < num_times; i++) 
f(arrs[i], arr_size);
gettimeofday(&end, NULL);

最新更新