Java似乎比C ++更快地执行简单算法。为什么?

时间:2011-06-21 16:04:19

标签: java c++ visual-c++ netbeans

简介:

使用两个相同的mergesort算法,我测试了C ++(使用Visual Studios C ++ 2010 express)和Java(使用NetBeans 7.0)的执行速度。我推测C ++执行至少会稍微快一些,但测试显示C ++执行速度比Java执行慢4到10倍。我相信我已经为C ++设置了所有的速度优化,我发布的是发布而不是调试。为什么会出现这种速度差异?

代码:

爪哇:

public class PerformanceTest1
{
 /**
  * Sorts the array using a merge sort algorithm
  * @param array The array to be sorted
  * @return The sorted array
  */
 public static void sort(double[] array)
 {
      if(array.length > 1)
      {
           int centre;
           double[] left;
           double[] right;
           int arrayPointer = 0;
           int leftPointer = 0;
           int rightPointer = 0;

           centre = (int)Math.floor((array.length) / 2.0);

           left = new double[centre];
           right = new double[array.length - centre];

           System.arraycopy(array,0,left,0,left.length);
           System.arraycopy(array,centre,right,0,right.length);

           sort(left);
           sort(right);

           while((leftPointer < left.length) && (rightPointer < right.length))
           {
                if(left[leftPointer] <= right[rightPointer])
                {
                     array[arrayPointer] = left[leftPointer];
                     leftPointer += 1;
                }
                else
                {
                     array[arrayPointer] = right[rightPointer];
                     rightPointer += 1;
                }
                arrayPointer += 1;
           }
           if(leftPointer < left.length)
           {
                System.arraycopy(left,leftPointer,array,arrayPointer,array.length - arrayPointer);
           }
           else if(rightPointer < right.length)
           {
                System.arraycopy(right,rightPointer,array,arrayPointer,array.length - arrayPointer);
           }
      }
 }

 public static void main(String args[])
 {
      //Number of elements to sort
      int arraySize = 1000000;

      //Create the variables for timing
      double start;
      double end;
      double duration;

      //Build array
      double[] data = new double[arraySize];
      for(int i = 0;i < data.length;i += 1)
      {
           data[i] = Math.round(Math.random() * 10000);
      }

      //Run performance test
      start = System.nanoTime();
      sort(data);
      end = System.nanoTime();

      //Output performance results
      duration = (end - start) / 1E9;
      System.out.println("Duration: " + duration);
 }
}

C ++:

#include <iostream>
#include <windows.h>
using namespace std;

//Mergesort
void sort1(double *data,int size)
{
if(size > 1)
{
    int centre;
    double *left;
    int leftSize;
    double *right;
    int rightSize;
    int dataPointer = 0;
    int leftPointer = 0;
    int rightPointer = 0;

    centre = (int)floor((size) / 2.0);
    leftSize = centre;
    left = new double[leftSize];
    for(int i = 0;i < leftSize;i += 1)
    {
        left[i] = data[i];
    }
    rightSize = size - leftSize;
    right = new double[rightSize];
    for(int i = leftSize;i < size;i += 1)
    {
        right[i - leftSize] = data[i];
    }

    sort1(left,leftSize);
    sort1(right,rightSize);

    while((leftPointer < leftSize) && (rightPointer < rightSize))
    {
        if(left[leftPointer] <= right[rightPointer])
        {
            data[dataPointer] = left[leftPointer];
            leftPointer += 1;
        }
        else
        {
            data[dataPointer] = right[rightPointer];
            rightPointer += 1;
        }
        dataPointer += 1;
    }
    if(leftPointer < leftSize)
    {
        for(int i = dataPointer;i < size;i += 1)
        {
            data[i] = left[leftPointer++];
        }
    }
    else if(rightPointer < rightSize)
    {
        for(int i = dataPointer;i < size;i += 1)
        {
            data[i] = right[rightPointer++];
        }
    }
            delete left;
            delete right;
}
}

void main()
{
//Number of elements to sort
int arraySize = 1000000;

//Create the variables for timing
LARGE_INTEGER start; //Starting time
LARGE_INTEGER end; //Ending time
LARGE_INTEGER freq; //Rate of time update
double duration; //end - start
QueryPerformanceFrequency(&freq); //Determinine the frequency of the performance counter (high precision system timer)

//Build array
double *temp2 = new double[arraySize];
QueryPerformanceCounter(&start);
srand((int)start.QuadPart);
for(int i = 0;i < arraySize;i += 1)
{
    double randVal = rand() % 10000;
    temp2[i] = randVal;
}

//Run performance test
QueryPerformanceCounter(&start);
sort1(temp2,arraySize);
QueryPerformanceCounter(&end);
    delete temp2;

//Output performance test results
duration = (double)(end.QuadPart - start.QuadPart) / (double)(freq.QuadPart);
cout << "Duration: " << duration << endl;

//Dramatic pause
system("pause");
}

观察:

对于10000个元素,C ++执行大约是Java执行时间的4倍。 对于100000个元素,比例约为7:1。 对于10000000个元素,该比率约为10:1。 对于超过10000000,Java执行完成,但C ++执行停止,我必须手动终止该进程。

8 个答案:

答案 0 :(得分:15)

我认为你运行程序的方式可能有误。当您在Visual C ++ Express 中按F5时,程序在调试器下运行,并且速度会慢很多。在其他版本的Visual C ++ 2010中(例如我使用的Ultimate),尝试按CTRL + F5(即启动而不调试)或尝试运行可执行文件本身(在Express中),你会看到差异。

我在我的机器上只运行了一次修改程序(添加了delete[] left; delete[] right;以消除内存泄漏;否则会以32位模式耗尽内存!)。我有一个i7 950.公平地说,我也将相同的数组传递给Java中的Arrays.sort()和C ++中的std :: sort。我使用的数组大小为10,000,000。

以下是结果(以秒为单位的时间):

Java code:            7.13
Java Arrays.sort:     0.93

32 bits
C++ code:             3.57
C++ std::sort         0.81

64 bits
C++ code:             2.77
C++ std::sort         0.76

因此C ++代码要快得多,甚至标准库(在Java和C ++中都经过高度调整)往往对C ++有轻微的优势。

编辑:我刚刚在原始测试中意识到,您在调试模式下运行C ++代码。你应该切换到发布模式并在调试器之外运行它(正如我在帖子中解释的那样)以获得公平的结果。

答案 1 :(得分:10)

我没有专业编程C ++(甚至不专业:)但我注意到你在堆上分配一个double(double * temp2 = new double [arraySize];)。与Java初始化相比,这是昂贵的,但更重要的是,它构成了内存泄漏,因为你永远不会删除它,这可以解释为什么你的C ++实现停止,它基本上耗尽了内存。

答案 2 :(得分:5)

首先,您是否尝试使用std::sort(或std::stable_sort通常是mergesort)来获得C ++的基准性能?

我不能评论Java代码,而是评论C ++代码:

  • 与Java不同,C ++中的new需要手动干预才能释放内存。每次递归都会泄漏记忆。我建议使用std::vector,因为它为您管理所有内存,iterator, iterator构造函数甚至会执行复制(并且可能比for循环更好地优化)。这几乎肯定是导致你的表现差异的原因。
  • 您在Java中使用arraycopy但在C ++中不使用库工具(std::copy),但如果您使用vector,这也无关紧要。
  • Nit:在您首次需要它们的位置声明并初始化您的变量,而不是全部位于函数顶部。
  • 如果您被允许使用标准库的某些部分,std::merge可以替换您的合并算法。

编辑:如果你真的使用说delete left;来清理可能是你问题的内存。正确的语法是delete [] left;来解除分配数组。

答案 3 :(得分:4)

你的版本泄漏了太多内存,时间毫无意义。

我确信时间花在了内存分配器上 重写它以使用标准C ++对象进行内存管理std :: vector,看看会发生什么。

我个人仍然希望Java版本能够获胜(仅仅)。因为JIT允许机器特定的优化,而C ++通常可以进行机器特定的优化,它只会进行通用的架构优化(除非你提供精确的架构标志)。

  • 注意:不要忘记在启用优化的情况下进行编译。

只是清理你的C ++:
我没有尝试用C ++风格进行良好的合并排序(只是重写)

void sort1(std::vector<double>& data)
{
    if(data.size() > 1)
    {
        std::size_t         centre    = data.size() / 2;
        std::size_t         lftSize   = centre;
        std::size_t         rhtSize   = data.size() - lftSize;

        // Why are we allocating new arrays here??
        // Is the whole point of the merge sort to do it in place?
        // I forget bbut I think you need to go look at a knuth book.
        //
        std::vector<double> lft(data.begin(),           data.begin() + lftSize);
        std::vector<double> rht(data.begin() + lftSize, data.end());

        sort1(lft);
        sort1(rht);
        std::size_t dataPointer   = 0;
        std::size_t lftPointer    = 0;
        std::size_t rhtPointer    = 0;

        while((lftPointer < lftSize) && (rhtPointer < rhtSize))
        {                                                                               
            data[dataPointer++] = (lft[lftPointer] <= rht[rhtPointer])
                                    ?  lft[lftPointer++]
                                    :  rht[rhtPointer++];
        }
        std::copy(lft.begin() + lftPointer, lft.end(), &data[dataPointer]);
        std::copy(rht.begin() + rhtPointer, rht.end(), &data[dataPointer]);
    }
}

考虑合并排序。我会试试这个:
我没有测试过,所以它可能无法正常工作。这是尝试不继续分配大量内存来进行排序。相反,它使用单个临时区域,并在排序完成后将结果复制回来。

void mergeSort(double* begin, double* end, double* tmp)
{
    if (end - begin <= 1)
    {   return;
    }

    std::size_t size    = end - begin;
    double*     middle  = begin +  (size / 2);

    mergeSort(begin, middle, tmp);
    mergeSort(middle, end, tmp);

    double* lft    = begin;
    double* rht    = middle;
    double* dst    = tmp;
    while((lft < middle) && (rht < end))
    {
        *dst++  = (*lft < *rht)
                        ? *lft++
                        : *rht++;
    }
    std::size_t count   = dst - tmp;
    memcpy(begin,          tmp, sizeof(double) * count);
    memcpy(begin + count,  lft, sizeof(double) * (middle - lft));
    memcpy(begin + count,  rht, sizeof(double) * (end    - rht));
}

void sort2(std::vector<double>& data)
{
    double*     left    = &data[0];
    double*     right   = &data[data.size()];

    std::vector<double> tmp(data.size());

    mergeSort(left,right, &tmp[0]);
}

答案 4 :(得分:2)

有几件事。

Java经过高度优化,在代码执行完毕后,JIT编译器会将代码执行为本机代码。

Java中的System.arraycopy执行速度要比简单地一次复制一个元素要快得多。尝试用memcpy替换这个副本,你会发现它更快。

编辑: 看看这篇文章:C++ performance vs. Java/C#

答案 5 :(得分:1)

从查看代码很难说,但我猜测原因在于处理递归而不是实际计算。尝试使用一些依赖于迭代而不是递归的排序算法,并分享性能比较的结果。

答案 6 :(得分:0)

我不知道为什么Java在这里要快得多。

我将它与内置的Arrays.sort()进行了比较,它再次快了4倍。 (它不会创建任何对象)。

通常,如果有一个测试,Java的速度要快得多,因为Java在删除无法执行任何操作的代码方面要好得多。

也许你可以使用memcpy而不是最后一个循环。

答案 7 :(得分:0)

尝试将全局向量作为缓冲区,并尝试不分配大量内存。 这将比你的代码运行得更快,因为如果使用一些技巧(只使用一个缓冲区并且在程序启动时分配内存,那么内存不会被分段):

#include <cstdio>
#define N 500001

int a[N];
int x[N];
int n;

void merge (int a[], int l, int r)
{
    int m = (l + r) / 2;
    int i, j, k = l - 1;
    for (i = l, j = m + 1; i <= m && j <= r;)
        if (a[i] < a[j])
            x[++k] = a[i++];
        else
            x[++k] = a[j++];
    for (; i <= m; ++i)
        x[++k] = a[i];
    for (; j <= r; ++j)
        x[++k] = a[j];
    for (i = l; i <= r; ++i)
        a[i] = x[i];
}

void mergeSort (int a[], int l, int r)
{
    if (l >= r)
        return;
    int m = (l + r) / 2;
    mergeSort (a, l, m);
    mergeSort (a, m + 1, r);
    merge (a, l, r);
}

int main ()
{
    int i;
    freopen ("algsort.in", "r", stdin);
    freopen ("algsort.out", "w", stdout);
    scanf ("%d\n", &n);
    for (i = 1; i <= n; ++i)
        scanf ("%d ", &a[i]);
    mergeSort (a, 1, n);
    for (i = 1; i <= n; ++i)
        printf ("%d ", a[i]);
    return 0;
}