C性能和编译选项

时间:2014-01-10 17:00:46

标签: java c performance gcc assembly

我有两个类似的实现(java和c ++),用于像选择排序这样的简单算法。

public interface SortingAlgorithm {

    public void sort(int[] a);
}

public class SelectionSort implements SortingAlgorithm {

    @Override
    public void sort(int[] a) {
        for (int i = 0; i < a.length; i++) {
            int lowerElementIndex = i;
            for (int j = i + 1; j < a.length; j++) {
                if (a[j] < a[lowerElementIndex]) {
                    lowerElementIndex = j;
                }
            }
            swap(a, lowerElementIndex, i);
        }
    }

    private void swap(int[] a, int i, int j) {
        if (i == j) {
            return;
        }
        int temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }
}

和c one:

inline void swap(int* a, int i, int j);

void s_sort(int* a, int size) {
  int i;
  for (i = 0; i < size; i++) {
    int lowerElementIndex = i, j;
    for (j = i + 1; j < size; j++) {
      if (a[j] < a[lowerElementIndex]) {
    lowerElementIndex = j;
      }
    }
    swap(a, lowerElementIndex, i);
  }
}

inline void swap(int* a, int i, int j) {
  if (i == j) {
    return;
  }
  int temp = a[i];
  a[i] = a[j];
  a[j] = temp;
}

现在,我尝试在大型阵列上测试它们(100000随机int)。 最初的结果是 java:~17秒(用oracle jdk / jvm编译和执行) c:~22秒(使用gcc v4.8编译,没有任何优化)

当然,然后我尝试通过cflags优化我的c版本。 结果是(我只报告cflags): -O1:~18.4

-O2:~18.4

-O {3-9}:~20.9

现在,我的第一个问题是我应该使用哪些cflacs进行编译?

所以我阅读了关于优化的gnu手册。 添加-march = native没有帮助。经过一段时间尝试其他选项后,我进入了-fprofile-arcs选项。将它添加到我的标志使我的代码在大约11秒内完成测试! 但是有些文件出现在我的文件夹中:分析的结果。据我所知,我应该将它们与-fbranch-probability一起使用并重新编译代码。 在~18.5秒内再次重新编译结果。 这就是我真正想问的问题。

如果我的程序必须写入文件并收集分析信息,那么它的运行速度如何,如果它没有运行速度慢1.5倍?

我忘了提到我在安装了Intel Celeron @ 2.8GHz处理器和linux(带有xfce的fedora 20)的旧PC上。如果您需要有关硬件的其他信息,请询问! ;)

编辑: 我用于测试的代码是:

爪哇:

public class Test {

    public static void main(String[] args) {
        int[] a = new int[100000];
        int[] a2 = new int[100000];
        for (int i = 0; i < a.length; i++) {
            a[i] = (int)(Math.random()*100000);
            a2[i] = a[i];
        }
        SelectionSort s = new SelectionSort();
        InsertionSort s1 = new InsertionSort();
        double start = System.nanoTime();
        s.sort(a);
        double end = System.nanoTime();
        double time = (end-start)/1000000000.0; 
        System.out.println("Selection: "+time);
        start = System.nanoTime();
        s1.sort(a2);
        end = System.nanoTime();
        time = (end-start)/1000000000.0;
        System.out.println("Insertion: "+time);
    }
}

c:

#include "insertion_sort.h"
#include "selection_sort.h"
#include <time.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main() {
  int max = 100000, i;
  srand(time(NULL));

  int array[100000], array2[100000];
  for(i=0; i<100000; i+=1) {
    array[i] = rand()%100000;
  }

  memcpy(array2, &array[0], 100000 * sizeof(int));

  clock_t inizio = clock();
  s_sort(array, max);
  clock_t fine = clock();
  float tempoEsecuzione = (float)(fine - inizio) / CLOCKS_PER_SEC;
  printf("Selection: %2.3f\n", tempoEsecuzione);

  inizio = clock();
  i_sort(array2, max);
  fine = clock();
  tempoEsecuzione = (float)(fine - inizio) / CLOCKS_PER_SEC;
  printf("Insertion: %2.3f\n", tempoEsecuzione);
  return 0;
}

代码包含对插入排序函数的引用,我没有将其包含在问题的其余部分中,因为(正如预期的那样)java运行速度慢于c。

4 个答案:

答案 0 :(得分:2)

不是答案,但评论的时间太长了。

您的Java基准测试远非最佳 - 特别是,您不允许JVM进行足够的预热。通过适当的预热,在我的机器上,时间下降了50%(4s vs 8s)。我建议的代码(只有SelectionSort):

public static void main(String[] args) {
    SelectionSort s = new SelectionSort();

    int[] aWarmUp = new int[10];
    int[] a = new int[100000];
    for (int i = 0; i < aWarmUp.length; i++) {
        aWarmUp[i] = (int)(Math.random()*100000);
    }
    for (int i = 0; i < a.length; i++) {
        a[i] = (int)(Math.random()*100000);
    }

    measure(s, a, "Before warmup ");

    for (int i = 0; i < 10000; i++) { //warmup
        s.sort(aWarmUp);
    }


    for (int i = 1; i < 5; i++) {
        System.gc(); //gc before measurement
        //re-fill the array with random numbers
        for (int j = 0; j < a.length; j++) {
            a[j] = (int)(Math.random()*100000);
        }
        measure(s, a, "In loop ");
        System.out.println(a[123]); //use the result
    }
}

private static void measure(SelectionSort s, int[] a, String msg) {
    double start = System.nanoTime();
    s.sort(a);
    double end = System.nanoTime();
    double time = (end-start)/1000000000.0;
    System.out.println(msg + time);
}

输出:

  

热身之前7.851840908
  在循环4.055204123中   循环3.878436395
  在循环3.880136077中   在循环3.882814287

答案 1 :(得分:2)

  

这就是我真正想问的问题。

     

如果必须编写程序,我的程序如何运行得如此之快   文件和收集分析信息,而不是它运行1.5次   什么时候没有?

是的,这是真正的问题。提及所有Java比较内容只会增加噪音。

我可以使用gcc 4.7.2在我的机器上重现奇怪的行为。毫不奇怪,代码的热门路径是内部for循环:

for (j = i + 1; j < size; j++) {
  if (a[j] < a[lowerElementIndex]) {
    lowerElementIndex = j;
}

相应生成的汇编代码中唯一相关的区别是:

快速案例:

    cmpl    %esi, %ecx
    jge .L3
    movl    %ecx, %esi
    movslq  %edx, %rdi
.L3:

慢的情况:

cmpl    %ecx, %esi
cmovl   %edx, %edi
cmovl   %esi, %ecx

第一种情况(快速)可以从branch prediction大大受益,但另一种情况(慢速情况)显然不能。排序或随机混洗的数组不会导致branch mispredictions太多。在这种情况下,第一个代码段是最佳的。

事实证明,实际上很难创建一个导致选择排序中出现大量分支错误预测的数据集。 (Yakk指出了这一点;另见my attempts创建一个邪恶的数据集;到目前为止,我没有创建一个。)

-fprofile-arcs碰巧禁用树矢量化,这似乎是生成慢速代码的原因。禁用树矢量化的更好方法是传递-fno-tree-vectorize标志。

clang 3.4还会生成快速案例代码,没有任何特殊标志。没有预热的Java代码运行速度比C代码慢2.4倍。 (由于这不是问题,我没有考虑改进Java代码性能。)

答案 2 :(得分:0)

以下是我得到的结果。对我来说(gcc 4.6.3)-O3 -funroll-loops胜利。

> gcc -o s s.c                                               
> time ./s                                                   
Elapsed time: 13
./s  13.08s user 0.00s system 99% cpu 13.088 total

> gcc -o s s.c -O1                                           
> time ./s                                                   
Elapsed time: 16
./s  16.02s user 0.00s system 99% cpu 16.042 total

> gcc -o s s.c -O2                                           
> time ./s                                                   
Elapsed time: 16
./s  16.06s user 0.00s system 99% cpu 16.076 total

> gcc -o s s.c -O3                                           
> time ./s                                                   
Elapsed time: 7
./s  7.38s user 0.00s system 99% cpu 7.381 total

> gcc -o s s.c -O3 -funroll-loops                            
> time ./s                                                   
Elapsed time: 6
./s  6.04s user 0.00s system 99% cpu 6.046 total

(注意:&#34;经过时间&#34;行不包括构建测试阵列所花费的时间 - 但它可以忽略不计)。

答案 3 :(得分:-1)

如果我从交换函数中删除条件,我在C程序中获得100%加速(使用gcc -O2构建)。即:

static inline void swap(int* a, int i, int j) {
  int temp = a[i];
  a[i] = a[j];
  a[j] = temp;
}

生成的代码可能对分支预测或缓存预取非常敏感,因此生成的代码似乎只是略有不同(例如,受不同编译器标志影响)可能会产生巨大的影响。

请注意,此程序中-fprofile-arcs的开销很小。您自己的时间测量也不包括写出分析文件,但是与5或10秒以上的执行时间相比,即使写出数据也需要花费很少的时间。