为什么我的C ++代码比LeetCode上的C等效代码慢三倍?

时间:2015-04-08 18:08:15

标签: c++ c

我一直在做一些LeetCode problems,我注意到C解决方案比C ++中的解决方案快几倍。例如:

更新了几个更简单的示例:

  

给定排序数组和目标值,如果找到目标,则返回索引。如果没有,请返回索引按顺序插入的索引。您可以假设数组中没有重复项。 (Link to question on LeetCode

我在C中的解决方案,运行时间为3毫秒:

int searchInsert(int A[], int n, int target) {
    int left = 0;
    int right = n;
    int mid = 0;
    while (left<right) {
        mid = (left + right) / 2;
        if (A[mid]<target) {
            left = mid + 1;
        }
        else if (A[mid]>target) {
            right = mid;
        }
        else {
            return mid;
        }
    }
    return left;
}

我的其他C ++解决方案,完全相同,但作为Solution类的成员函数在13毫秒内运行:

class Solution {
public:
    int searchInsert(int A[], int n, int target) {
        int left = 0;
        int right = n;
        int mid = 0;
        while (left<right) {
            mid = (left + right) / 2;
            if (A[mid]<target) {
                left = mid + 1;
            }
            else if (A[mid]>target) {
                right = mid;
            }
            else {
                return mid;
            }
        }
        return left;
    }
};

更简单的例子:

  

反转整数的数字。如果结果溢出,则返回0。 (Link to question on LeetCode

C版本在6毫秒内运行:

int reverse(int x) {
    long rev = x % 10;
    x /= 10;
    while (x != 0) {
        rev *= 10L;
        rev += x % 10;
        x /= 10;
        if (rev>(-1U >> 1) || rev < (1 << 31)) {
            return 0;
        }
    }
    return rev;
}

C ++版本完全相同,但作为Solution类的成员函数,运行时间为19毫秒:

class Solution {
public:
    int reverse(int x) {
        long rev = x % 10;
        x /= 10;
        while (x != 0) {
            rev *= 10L;
            rev += x % 10;
            x /= 10;
            if (rev>(-1U >> 1) || rev < (1 << 31)) {
                return 0;
            }
        }
        return rev;
    }
};

如果LeetCode测试系统没有在启用优化的情况下编译代码,我会看到如何在原始示例中使用矢量矢量作为2D数组会产生相当大的开销。但上面更简单的例子不应该遇到这个问题,因为数据结构非常原始,特别是在第二种情况下,你所拥有的只是长整数或整数算术。那仍然慢了三倍。

我开始认为LeetCode一般会进行基准测试的方式可能会发生奇怪的事情,因为即使在整数反转问题的C版本中,只需更换线路,您的运行时间就会大大增加     if(rev&gt;( - 1U&gt;&gt; 1)|| rev&lt;(1&lt;&lt; 31)){ 同     if(rev&gt; INT_MAX || rev&lt; INT_MIN){

现在,我认为必须#include<limits.h>可能与此有关,但似乎有点极端,这个简单的改变将执行时间从6毫秒减少到19毫秒。

2 个答案:

答案 0 :(得分:42)

最近我在C ++中使用2d数组时看到了vector<vector<int>>很多建议,而且我已经向人们指出为什么这不是一个好主意。知道何时将临时代码拼凑在一起是一个方便的技巧,但是(几乎)从来没有任何理由将其用于实际代码。 right thing to do是使用包装连续内存块的类。

所以我的第一反应可能是指出这是造成这种差异的可能来源。但是,您还在C版本中使用int**,这通常表示与vector<vector<int>>完全相同的问题。

所以我决定只比较两种解决方案。

http://coliru.stacked-crooked.com/a/fa8441cc5baa0391

  

6468424
  6588511

&#39; C版本所花费的时间&#39; vs&#39; C ++版本&#39;以纳秒为单位。

我的结果不会显示您描述的差异。然后我突然想到检查人们在基准测试时犯的一个常见错误

http://coliru.stacked-crooked.com/a/e57d791876b9252b

  

18386695
  42400612

请注意,第一个示例中的-O3标志已成为-O0,这会禁用优化。

结论:您可能正在比较未经优化的可执行文件。

C ++支持构建不需要开销的丰富抽象,但是消除开销确实需要某些代码转换,这会对“可调试性”造成严重破坏。代码。

这意味着调试版本避免了那些转换,因此C ++调试版本通常比C样式代码的调试版本慢,因为C样式代码并没有使用太多的抽象。如果计时,例如使用函数调用代替简单存储指令的机器代码,那么看到如上所述的130%减速并不令人惊讶。


某些代码确实需要优化才能在调试时获得合理的性能,因此编译器通常会提供一种模式,该模式应用一些不会给调试器带来太多麻烦的优化。为此,Clang和gcc使用-O1,您可以看到即使是这种优化级别,也基本上消除了C风格代码与更多C ++风格代码之间的差距:

http://coliru.stacked-crooked.com/a/13967ebcfcfa4073

  

8389992
  8196935


<强>更新

在后面的例子中,优化不应该有所作为,因为C ++没有使用除C版本之外的任何抽象。我猜测对此的解释是,这些示例是使用不同的编译器或其他一些不同的编译器选项编译的。在不知道编译是如何完成的情况下,我会说比较这些运行时数是没有意义的; LeetCode显然没有产生苹果与苹果的比较。

答案 1 :(得分:-4)

您正在使用C ++代码段中的向量向量。向量是C ++中的序列容器,类似于可以改变大小的数组。如果您使用静态分配的数组,而不是vector<vector<int>>,那会更好。您可以使用自己的Array类以及operator []重载,但是当您添加的元素多于其原始大小时,vector会动态调整大小,因此vector会产生更多开销。在C ++中,如果将其与C进行比较,则使用按引用调用来进一步减少时间。如果写得好,C ++应该运行得更快。