11

我在这里听到很多人说 C++ 在所有方面都与 C 一样快或更快,但更干净、更好。

虽然我并不反对 C++ 非常优雅且速度非常快的事实,但我没有找到任何替代关键内存访问或受处理器限制的应用程序的方法。

问题:就性能而言,C 风格的数组在 C++ 中是否存在等价物?

下面的例子是人为的,但我对现实问题的解决方案很感兴趣:我开发图像处理应用程序,那里的像素处理量很大。

double t;

// C++ 
std::vector<int> v;
v.resize(1000000,1);
int i, j, count = 0, size = v.size();

t = (double)getTickCount();

for(j=0;j<1000;j++)
{
    count = 0;
    for(i=0;i<size;i++)
         count += v[i];     
}

t = ((double)getTickCount() - t)/getTickFrequency();
std::cout << "(C++) For loop time [s]: " << t/1.0 << std::endl;
std::cout << count << std::endl;

// C-style

#define ARR_SIZE 1000000

int* arr = (int*)malloc( ARR_SIZE * sizeof(int) );

int ci, cj, ccount = 0, csize = ARR_SIZE;

for(ci=0;ci<csize;ci++)
    arr[ci] = 1;

t = (double)getTickCount();

for(cj=0;cj<1000;cj++)
{
    ccount = 0;
    for(ci=0;ci<csize;ci++)
        ccount += arr[ci];      
}

free(arr);

t = ((double)getTickCount() - t)/getTickFrequency();
std::cout << "(C) For loop time [s]: " << t/1.0 << std::endl;
std::cout << ccount << std::endl;

结果如下:

(C++) For loop time [s]: 0.329069

(C) For loop time [s]: 0.229961

注意:getTickCount()来自第三方库。如果您想测试,只需替换为您最喜欢的时钟测量

更新:

我正在使用 VS 2010,发布模式,其他一切默认

4

6 回答 6

12

简单的回答:您的基准测试存在缺陷。

更长的答案:您需要打开完全优化才能获得 C++ 性能优势。然而,您的基准仍然存在缺陷。

一些观察:

  1. 如果您打开完全优化,则会删除大量的 for 循环。这使您的基准测试毫无意义。
  2. std::vector有动态重新分配的开销,请尝试std::array.
    具体来说,微软的stl默认检查了迭代器。
  3. 您没有任何障碍可以防止 C/C++ 代码/基准代码之间的交叉重新排序。
  4. (不是真正相关的)cout << ccount是区域设置感知的,printf不是; std::endl冲洗输出,printf("\n")不要。

显示 c++ 优势的“传统”代码是 C qsort()vs C++ std::sort()。这就是代码内联大放异彩的地方。

如果您想要一些“现实生活”的应用程序示例。搜索一些光线追踪器或矩阵乘法的东西。选择一个进行自动矢量化的编译器。

更新 使用LLVM 在线演示,我们可以看到整个循环被重新排序。基准代码移动到开始,并跳转到第一个循环中的循环结束点,以便更好地进行分支预测:

(这是 C++ 代码)

######### jump to the loop end
    jg  .LBB0_11
.LBB0_3:                                # %..split_crit_edge
.Ltmp2:
# print the benchmark result
    movl    $0, 12(%esp)
    movl    $25, 8(%esp)
    movl    $.L.str, 4(%esp)
    movl    std::cout, (%esp)
    calll   std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
.Ltmp3:
# BB#4:                                 # %_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc.exit
.Ltmp4:
    movl    std::cout, (%esp)
    calll   std::basic_ostream<char, std::char_traits<char> >& std::basic_ostream<char, std::char_traits<char> >::_M_insert<double>(double)
.Ltmp5:
# BB#5:                                 # %_ZNSolsEd.exit
    movl    %eax, %ecx
    movl    %ecx, 28(%esp)          # 4-byte Spill
    movl    (%ecx), %eax
    movl    -24(%eax), %eax
    movl    240(%eax,%ecx), %ebp
    testl   %ebp, %ebp
    jne .LBB0_7
# BB#6:
.Ltmp52:
    calll   std::__throw_bad_cast()
.Ltmp53:
.LBB0_7:                                # %.noexc41
    cmpb    $0, 28(%ebp)
    je  .LBB0_15
# BB#8:
    movb    39(%ebp), %al
    jmp .LBB0_21
    .align  16, 0x90
.LBB0_9:                                #   Parent Loop BB0_11 Depth=1
                                        # =>  This Inner Loop Header: Depth=2
    addl    (%edi,%edx,4), %ebx
    addl    $1, %edx
    adcl    $0, %esi
    cmpl    %ecx, %edx
    jne .LBB0_9
# BB#10:                                #   in Loop: Header=BB0_11 Depth=1
    incl    %eax
    cmpl    $1000, %eax             # imm = 0x3E8
######### jump back to the print benchmark code
    je  .LBB0_3

我的测试代码:

std::vector<int> v;
v.resize(1000000,1);
int i, j, count = 0, size = v.size();

for(j=0;j<1000;j++)
{
    count = 0;
    for(i=0;i<size;i++)
         count += v[i];     
}

std::cout << "(C++) For loop time [s]: " << t/1.0 << std::endl;
std::cout << count << std::endl;
于 2012-08-29T08:19:34.043 回答
12

问题:就性能而言,C++ 中是否存在与 C 样式数组等效的功能?

答:写C++代码!了解您的语言,了解您的标准库并使用它。标准算法是正确的、可读的和快速的(他们知道如何在当前编译器上快速实现它)。

void testC()
{
    // unchanged
}

void testCpp()
{
    // unchanged initialization

    for(j=0;j<1000;j++)
    {
        // how a C++ programmer accumulates:
        count = std::accumulate(begin(v), end(v), 0);    
    }

    // unchanged output
}

int main()
{
    testC();
    testCpp();
}

输出:

(C) For loop time [ms]: 434.373
1000000
(C++) For loop time [ms]: 419.79
1000000

g++ -O3 -std=c++0x在 Ubuntu 上使用 4.6.3 版编译。

对于您的代码,我的输出与您的相似。user1202136 对差异给出了很好的答案......

于 2012-08-29T08:35:26.067 回答
8

这似乎是一个编译器问题。对于 C 数组,编译器检测模式,使用自动矢量化并发出 SSE 指令。对于vector来说,它似乎缺乏必要的智能。

如果我强制编译器不使用 SSE,结果非常相似(用 测试g++ -mno-mmx -mno-sse -msoft-float -O3):

(C++) For loop time [us]: 604610
1000000
(C) For loop time [us]: 601493
1000000

这是生成此输出的代码。它基本上是您问题中的代码,但没有任何浮点。

#include <iostream>
#include <vector>
#include <sys/time.h>

using namespace std;

long getTickCount()
{
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return tv.tv_sec * 1000000 + tv.tv_usec;
}

int main() {
long t;

// C++ 
std::vector<int> v;
v.resize(1000000,1);
int i, j, count = 0, size = v.size();

t = getTickCount();

for(j=0;j<1000;j++)
{
    count = 0;
    for(i=0;i<size;i++)
         count += v[i];     
}

t = getTickCount() - t;
std::cout << "(C++) For loop time [us]: " << t << std::endl;
std::cout << count << std::endl;

// C-style

#define ARR_SIZE 1000000

int* arr = new int[ARR_SIZE];

int ci, cj, ccount = 0, csize = ARR_SIZE;

for(ci=0;ci<csize;ci++)
    arr[ci] = 1;

t = getTickCount();

for(cj=0;cj<1000;cj++)
{
    ccount = 0;
    for(ci=0;ci<csize;ci++)
        ccount += arr[ci];      
}

delete arr;

t = getTickCount() - t;
std::cout << "(C) For loop time [us]: " << t << std::endl;
std::cout << ccount << std::endl;
}
于 2012-08-29T08:23:50.123 回答
4

动态大小数组的 C++ 等效项是std::vector. std::array固定大小数组的 C++ 等价物将是std::tr1::arrayC++11 之前的版本。

如果您的向量代码没有重新调整大小,那么如果您在编译时启用了一些优化,那么很难看出它比使用动态分配的 C 数组要慢得多。

注意:运行发布的代码,在 x86 上的 gcc 4.4.3 上编译,编译器选项

g++ -Wall -Wextra -pedantic-errors -O2 -std=c++0x

结果可重复地接近

(C++) For 循环时间 [us]: 507888

1000000

(C) 循环时间 [us]:496659

1000000

std::vector因此,经过少量试验后,变体的速度似乎慢了约 2% 。我会考虑这种兼容的性能。

于 2012-08-29T08:05:25.907 回答
0

您指出的是,访问对象总是会带来一些开销,因此访问 avector不会比访问一个好的旧数组更快。

但即使使用数组是“C 风格的”,它仍然是 C++,所以不会有问题。

然后,正如@juanchopanza 所说,std::array在 C++11 中有可能比 更有效std::vector,但专门用于固定大小的数组。

于 2012-08-29T08:09:26.740 回答
0

通常编译器会做所有的优化......你只需要选择一个好的编译器

于 2012-08-29T08:21:04.630 回答