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

介绍:

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

码:

Java的:

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  #include  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 ++执行停止,我必须手动终止进程。

我认为你运行程序的方式可能有误。 当你在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代码:7.13
 Java Arrays.sort:0.93

 32位
 C ++代码:3.57
 C ++ std :: sort 0.81

 64位
 C ++代码:2.77
 C ++ std :: sort 0.76

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

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

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

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

我不能评论Java代码,但对于C ++代码:

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

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

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

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

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

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

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

 void sort1(std::vector& 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 lft(data.begin(), data.begin() + lftSize); std::vector 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& data) { double* left = &data[0]; double* right = &data[data.size()]; std::vector tmp(data.size()); mergeSort(left,right, &tmp[0]); } 

有几件事。

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

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

编辑:看看这篇文章: C ++性能与Java / C#

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

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

 #include  #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; } 

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

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

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

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