【问题标题】:C++ equivalent for C-style arrayC 样式数组的 C++ 等效项
【发布时间】:2012-08-23 18:57:13
【问题描述】:

我在这里听到很多人说 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,发布模式,其他一切默认

【问题讨论】:

  • 我怀疑您是否进行了全面优化的 C++ 构建。
  • 这段代码中没有指针数组...
  • 我刚刚对此进行了测试(在一台计算机上使用稍微修改的代码,使用一个特定的编译器)。如果没有优化,“C++ 风格”比“C 风格”慢三分之一。通过优化,“C++ 风格”始终比“C 风格”更快(两者都比没有优化时快得多)。
  • 我的机器上的 C 和 C++ 版本都得到 0.215 秒。 (x86-64 机器上的 GCC 和 G++)。出于某种原因 (.534/.605),C++ 在 32 位上更快。
  • 使用 g++ -O3 编译时,两个版本的运行时相同。

标签: c++ c performance optimization


【解决方案1】:

问题:就性能而言,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

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

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

【讨论】:

  • 您能发布您的时间分析代码吗?当我使用 std::accumulate 时,我得到了非常奇怪的结果。
  • @juanchopanza 我从&lt;sys/time.h&gt;使用gettimeofday
  • 我认为这就是答案......虽然在我的测试平台(MSVC)上它比其他任何东西都慢。但我不能责怪 C++ 似乎是微软的错
【解决方案2】:

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

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

一些观察:

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

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

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

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

(这是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;

【讨论】:

  • 嗯,我在 MSVC 中使用了默认的发布模式。我试图摆脱所有分配/输出等的循环。std::cout 战略性地超出了基准。这似乎是一个编译器问题,因为 g++ 为两个代​​码提供了相同的时间
  • 你是说他“拿错了”吗?他的代码是一个有效的基准,它应该为两种语言生成相同的指令。这不是编译器或 STL 中的问题。
  • (2) 不正确。数组在时间检查之前会提前调整大小,因此不会执行重新分配。至于(1)我会(部分)展开循环(以减少与循环相关的东西)并使计数不稳定以排除不需要的优化
  • @user396672,使ccount volatile 也会阻止矢量化。我们需要停止循环反转/消除,而不是矢量化。
  • @J-16SDiZ 2010 年 _SECURE_SCL 的默认值为 0 msdn.microsoft.com/en-us/library/aa985896.aspx
【解决方案3】:

这似乎是一个编译器问题。对于 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;
}

【讨论】:

  • 这与 SSE 或 MMX 无关。这只是关于打开优化。此外,gettimeofday() 是一个不好的基准测试选择,因为它还记录了在流程之外花费的时间。如果您使用的是 unix,请使用 getrusage()。
  • @LutherBlissett 对不起,SSE 或 MMX 的问题。我检查了汇编程序的输出。优化已经通过-O3 开启。我使用了gettimeofday,因为我听说getrusage 的分辨率很差。为了减少基准测试错误,我确保系统大部分时间处于空闲状态,并多次运行基准测试,结果相似。
【解决方案4】:

动态大小数组的 C++ 等效项是 std::vector。固定大小数组的 C++ 等价物是 std::arraystd::tr1::array pre-C++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%。我会考虑这种兼容的性能。

【讨论】:

  • 在向量初始化后开始分析,所以这不是问题...
  • 从零调整大小可能与分配没有什么不同。
  • @DavidSchwartz 可能,但这似乎没有必要,并且表明对 std::vector 了解不多。
  • ...但速度较慢,所以现在怎么办?
  • @LuboAntonov 我需要一些我可以测试的东西来证明它实际上更慢。运行我自己的使用 gcc 4.6 和 4.8 编译的代码版本,我在 C 和 C++ 版本中获得了相同的性能。
【解决方案5】:

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

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

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

【讨论】:

  • "C++11中有std::array,比std::vector效率高。"你能备份一下吗?
  • 如果你对一个改变其大小的向量进行操作,数组会更有效,例如在没有resize的情况下循环push_back
  • array 可能更有效,例如因为vector涉及额外的间接级别(它包含指向数据的指针,而array包含数据)。是的,编译器会在循环外提升对该数据指针的读取,但这样做会花费您一个寄存器(将其存储在其中),而对std::array 的访问可以直接基于堆栈指针。使用一个寄存器是一个较小的性能问题,但有时可能会有所不同。出于同样的原因,array 可能会更慢,因为使用更多堆栈会影响缓存。 “五月”是一个很容易达到的目标:-)
  • 如果不出意外,我预计std::array 通常会更快,因为堆栈分配通常优于堆分配,而std::array 的指针间接更少,如您所说。我希望通过指向动态分配的std::array 的指针进行访问与访问std::vector 相同。至少在我的许多领域中,堆分配/释放通常是最大的性能杀手。
【解决方案6】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-31
    • 2011-03-12
    • 1970-01-01
    • 2010-11-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多