【问题标题】:C vs C++ code optimization for simple array creation and i/o用于简单数组创建和 i/o 的 C 与 C++ 代码优化
【发布时间】:2014-01-29 18:52:16
【问题描述】:

我一直试图说服我的一个朋友避免使用动态分配的数组并开始转向 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 代码存在明显的内存泄漏......
  • C++ 从 -O2 中获益比 C 更多。
  • @Timo: true,但在这种情况下,进程会立即结束并将内存返回给操作系统。
  • 修复了内存泄漏 - 我向我的朋友指出这是使用 STL 的好处之一 - 更少的内存泄漏 :)
  • STL 向量也是动态分配的……通常遵循大小加倍算法。 STL 有很多优点,但这不是其中之一。

标签: c++ optimization compiler-construction io


【解决方案1】:

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

【讨论】:

  • 这个。如果你必须在那里做输出,让它公平竞争并使用 printf()。
【解决方案2】:

使用 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

【讨论】:

  • 这段代码不会花费几乎所有的时间来格式化浮点数吗?即使 ++i 优化不佳,它也会使用一小部分循环。
  • ... 如果您想比较迭代速度,请去掉打印。是浴缸里的鲸鱼。
【解决方案3】:

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

话虽如此,性能并不是一切 - 并非如此。使用 STL 提供了许多其他好处,包括:

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

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

【讨论】:

  • 实际上,STL vector 类被设计成与指针相比会导致(几乎)零开销。不过,对于 iostream,您可能是对的。
  • 是的 - 但几乎是 0 != 0。其中存在一些差异。对于 iostream,这是一个更大的差异。
  • 在我看来,使用向量有三种方法。 1)让它自然增长:重新分配的开销。 2)调整到需要的大小:零初始化所有项目的开销。 3) 预留所需大小:在push_back 期间仍需检查是否有足够空间的开销。也就是说,它比自己跟踪事情要方便得多,而且性能大多足够好。
  • 是的 - 我总是会投票使用它,但它确实与自己做事有(尽管很小)性能差异。
  • 我想知道人们在哪些方面存在强烈的反对意见,但......
【解决方案4】:

相信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)

【讨论】:

    【解决方案5】:

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

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

    【讨论】:

      【解决方案6】:

      我认为您甚至没有运行相同的代码。
      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;
      }
      

      【讨论】:

      • 我不同意。 C 示例中唯一可能失败的函数是 malloc 和 printf。如果 malloc 内存不足,他可能会归档,但在极少数情况下,无论如何你无能为力.. printf 理论上可以归档,但实际上永远不会。无论如何,添加错误检查与性能无关(它将执行得如此之快以至于不会产生影响)。至少但不是最后,内存将在退出时由操作系统回收,因此 free() 是没有必要的(它是 C++ 的“错误”,它无论如何都会释放!)。但是,再一次,在 5 秒的执行时间内只有一个 free():没关系。
      • 我尝试添加错误检查并再次运行time ./c &gt; /dev/null,它实际上运行得更快(显然不是因为额外的代码,而是因为中断的不确定性)。
      • @Koper:你真的认为不需要调用 free() 吗?即使它在进程退出时被操作系统释放,依赖它也是非常糟糕的编码实践。可以想象,由于不调用 free 导致的泄漏,长时间运行的程序可能会使用越来越多的内存。此外,并非所有操作系统都会在进程退出时回收内存,尤其是那些没有虚拟内存管理器的操作系统。实时嵌入式操作系统通常没有虚拟机,因此如果您没有正确管理内存,您将在此类平台上被淹没。
      • @Koper:如果你专门看一下这个函数'random_double'。那么很好,它永远不会失败。但是,如果您将其视为具有一些任意代码的通用函数,那么您需要某种方法来检查错误条件并强制循环退出并执行通用内存管理。
      • @Kopir:如果您将这段代码视为整个程序很好(C/C++ 中的十行程序肯定。维护为零,但在那种情况下,谁在乎 6 秒?)我在看在这里,它是一个真实应用程序的一部分,只是为了计时而提取的;在这种情况下,是的,您确实需要进行免费、所有其他检查、循环终止、错误传播、内存释放等。真正的应用程序需要的所有东西都需要显式添加到真正的 C 应用程序中,否则您就是没有对两段代码进行准确的比较。
      【解决方案7】:

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

      【讨论】:

        【解决方案8】:

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

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

        但我知道什么? ;)

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

        【讨论】:

        • 虽然您对这个问题的回答对我来说似乎是错误的,但 Thrust 链接当然值得一看!
        • @Koper:由于 printf 格式化浮点数,您甚至没有比较循环的性能。当你用多余的无关计算加载它时,你无法回答这个问题。
        猜你喜欢
        • 1970-01-01
        • 2015-05-24
        • 1970-01-01
        • 2014-10-28
        • 2018-06-29
        • 2011-09-20
        • 2013-05-07
        • 2023-03-17
        相关资源
        最近更新 更多