针对简单数组创建和 I/O 的 C 与 C++ 代码优化

问题描述 投票:0回答:8

我一直试图说服我的一个朋友避免使用动态分配的数组并开始转向 STL 向量。我向他发送了一些示例代码,以展示一些可以使用 STL 和函子/生成器完成的事情:

#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>

#define EVENTS 10000000

struct random_double {
  double operator() () { return (double)rand()/RAND_MAX; }
};  

int main(int argc, char **argv){

  std::vector<double> vd (EVENTS);

  generate(vd.begin(), vd.end(), random_double());
  copy(vd.begin(), vd.end(), std::ostream_iterator<double>(std::cout, "\n"));

  return 0;
} 

他对此的回复,虽然他觉得这样更优雅,但他自己的代码更快(几乎是 2 倍!)这是他回复的 C 代码:

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <string.h>

#define EVENTS 10000000

__inline double random_double() {
  return (double)rand()/RAND_MAX;
}


int main(int argc, char **argv){
  unsigned int i;
  double *vd;
  vd = (double *) malloc(EVENTS*sizeof(double));

  for(i=0;i<EVENTS;i++){ vd[i]=random_double(); }

  for(i=0;i<EVENTS;i++){ printf("%lf\n",vd[i]); }

  free(vd);

  return 0;
}

所以我运行了简单的计时测试来看看会发生什么,这就是我得到的结果:

> time ./c++test > /dev/null
real    0m14.665s
user    0m14.577s
sys     0m0.092s

> time ./ctest > /dev/null
real    0m8.070s
user    0m8.001s
sys     0m0.072s

使用 g++ 的编译器选项是:g++ -finline -funroll-loops。没什么特别的。谁能告诉我为什么 C++/STL 版本在这种情况下速度较慢?瓶颈在哪里,我能向我的朋友推销使用 STL 容器吗?

c++ performance optimization io
8个回答
19
投票

几乎可以肯定是使用 iostream 库与 printf()。如果您想对算法进行计时,您应该在循环之外进行输出。


17
投票

使用 printf:

  for (std::vector<double>::iterator i = vd.begin(); i != vd.end(); ++i)
     printf("%lf\n", *i);

结果是:

koper@elisha ~/b $ time ./cpp > /dev/null
real    0m4.985s
user    0m4.930s
sys     0m0.050s
koper@elisha ~/b $ time ./c > /dev/null
real    0m4.973s
user    0m4.920s
sys     0m0.050s

使用的标志:

-O2 -funroll-loops -finline


4
投票

使用 STL,尤其是在使用向量和其他不错的实用程序类时,可能总是比使用 malloc 和内联函数的手动 C 代码慢。 没有真正的解决办法。

话虽这么说,性能并不是一切——并非如此。 使用 STL 还具有许多其他好处,包括:

  1. 更好的可维护性:它更具表现力,因此您可以用更少的代码以更优雅、更干净的方式完成更多工作。
  2. 安全:使用向量比直接处理指针和 malloc 安全得多。
  3. 灵活性:例如,通过使用带有函子的向量,如果您需要动态扩展此集合,您会更容易。
  4. 生产力:通过使代码更简洁,STL 比许多单独的 C 例程执行类似功能更有效地促进重用。

你真的想争论在更高的抽象级别上工作 - 这里有一些权衡,通常是在性能方面,但几乎所有的开发都进入更高的抽象级别是有原因的;在大多数情况下,收获远比牺牲更有价值。


2
投票

相信

std::cout
的插入迭代器性能不佳,我尝试插入以下函子:

struct Print {
  void operator()( double d ) { printf("lf\n", d); }
};

并在 stl 容器上使用

for_each

 generate(vd.begin(), vd.end(), random_double());
  //copy(vd.begin(), vd.end(), std::ostream_iterator<double>(std::cout, "\n"));
  std::for_each(vd.begin(), vd.end(), Print() );

事实上,我现在得到了

time.exe raw_vs_stl.exe stl > t.txt
real    0m 2.48s
user    0m 1.68s
sys     0m 0.28s

对于 STL 版本...而“原始”版本的结果或多或少相同。

time.exe raw_vs_stl.exe raw > t.txt
real    0m 9.22s
user    0m 7.89s
sys     0m 

0.67秒 结论:向量性能与原始数组一样好。 使用起来更安全、更方便。

(免责声明:使用VC2005)


1
投票

了解两种实现之间的速度差异及其原因的一个技巧是深入研究程序集。装配实际上并没有那么可怕,它向您展示了到底发生了什么。它对于查看编译器优化了什么也非常方便。一般来说,更多的汇编指令 = 更长的时间,但请记住,某些指令比其他指令花费的时间要长得多。

在 Visual Studio(我怀疑还有许多其他 ID)中,有一个选项可以查看与相应 C++ 行交错的程序集。 (在 VC 中,这是“调试”->“Windows”->“反汇编”)。


1
投票

我认为你甚至没有运行相同的代码。
C 代码没有错误检查,并且会在异常时泄漏内存。
要进行票价比较,您需要让 C 程序执行 C++ 程序正在执行的操作。

bool errorNumber = 0;   // Need a way to pass error information back from the function
int main(int argc, char **argv)
{
    ......
    {
        vd[i]=random_double();
        //
        // In C++ this logic is implicit with the use of excptions.
        // Any example where you don't do error checking is not valid.
        // In real life any code has to have this logic built in by the developer
        //
        if (errorNumber != 0)
        {    break;
        }
    }
    ........
    free(vd);  The cost of freeing the memory is not zero that needs to be factored in.
    return 0;
}

0
投票

有时 STL 速度较慢,但手动滚动映射/集以进行多次插入/删除/查找将很难完成。
正如 Neil 指出的,在速度上,printf 胜过 iostream(也是 Scott Meyers“更有效的 C++,第 23 点”中的一个观点)。然而,在更复杂的系统中。能够在日志记录中写出完整的类是值得的。 printf 方法是将 sprintf 类信息放入函数中,并将其作为字符串参数传递给记录器。 这样的话,收益会更小。


-1
投票

在高性能情况下(例如游戏),明智的做法是避免使用 STL 容器。是的,它们提供了出色的功能,但也带来了一些开销。这可能是灾难性的。

就我个人而言,我只使用 std 的文件处理和偶尔的向量。

但是我知道什么? ;)

编辑: 让您的朋友看一下 Thrust,它尝试提供类似 STL 的功能以在 GPU 上进行计算。

© www.soinside.com 2019 - 2024. All rights reserved.