C++与C哪个慢,要看具体的代码

phy0292 2017-09-27 12:24:32
看了几篇争论C++比C慢的帖子,发现有些人给出的C++比C慢的代码并不是和原作者一样的结果,而是恰恰相反!比如我从别处复制的如下代码:

#include <stdio.h>
#include <string.h>
#include <time.h>
#include <string>
using namespace std;
int i,j;
char cs[10001];
string cpps;
clock_t t;
void main() {
t=clock();
for (i=0;i<10;i++) {
cs[0]=0;
for (j=0;j<10000;j++) {
strcat(cs,"a");
}
}
printf(" cs %lg\n",(double)(clock()-t));
//----------------C和C++分割线-------------
t=clock();
for (i=0;i<10;i++) {
cpps="";
for (j=0;j<10000;j++) {
cpps=cpps+"a";
}
}
printf("cpps %lg\n",(double)(clock()-t));
}
// cs 125
//cpps 203

比如上面这段代码,原作者输出C语言消耗时间是125毫秒,C++消耗时间是203毫秒。
但我运行上段代码得到的结果恰恰相反,cs 200 cpps 30,反复试几次,结果差不多。如果大家不信也可以试试,但前提条件是请在Release方式下运行,不要在Debug方式下运行。如果在Debug方式下运行,结果又会反转,因为Debug方式下,标准库加了不少调试用的附加代码。
如果把上面这段代码再优化一下:

cpps=cpps+"a";

改成
cpps += "a";

或者改成
cpps.append("a");

那么C++又会快更多,结果是C 200毫秒,C++ 1毫秒,反复试几次,也就这样的数量级。
争论的帖子里都说C++的对象因为构造、析构、动态内存分配等等原因,导致C++比C慢,但结果为什么相反?而有的人连代码都没有试也随声附和,所以看问题要亲自测试亲眼所见再下结论才更科学。
然而上面这段代码初一看,感觉C无论如何也要比C++快,并且要快很多,但为什么结果相反呢,经过分析,恰恰是string的类包含了一些重要的信息才导致C++反而要快很多。

现在我分析一下原因:
在上面代码的C实现中,最耗时的是strcat函数,200毫秒的时间都是耗在这个函数上。大家都知道这个函数是把参数2的字符串追加到参数1的后面。它的实现过程是这样的,从参数1的第一个字符开始一直找到末尾的\0,然后把参数2复制到这个末尾。也就是说它每次追加前都要从第一个字符开始遍历一下参数1中的所有字符,直到找到末尾在哪。当这个字符串变得越来越长的时候,这个无谓的时间也就消耗的越来越多,实际上它用的是蛮力。而在本例中,字符串最长时有10000字节。也就是说它平均每次要遍历5000字节,请注意是平均每次。
再来看看C++标准库中string的实现,cpps += "a";它也是把后面的字符串追加到前面的string中,在追加的时候,它已经知道自己的末尾在哪里,因为它的末尾是end(),或者是begin()+size(),所以string就不用像strcat那样傻傻地从头到尾找一遍末尾在哪,而是直接把字符串追加到自己的后面。当然如果在追加后长度超出来了自己力所能及的范围,也就是追加后总长度要大于capacity()的话,那么它就会重新分配更大的内存,这就是长度动态增长,当然这会消耗时间。而在我们上面这段代码中,在第一次内循环结束后,cpps实际分配的内存已经大于等于10001字节了,在第二次内循环时,它就不用再分配内存了,因为它已经足够容纳接下来每次循环所要求的长度。那有人会说,内循环外不是有个cpps = "";吗,分配的长度不会重新缩回去吗。不用担心,不会发生这样的事,容器中分配的内存只会增长不会缩小,像cpps = ""或cpps.clear(),只是让size=0,分配的内存原样不动。所以上面的代码在第一次内循环时消耗了一点分配内存的时间,而接下来这点时间也省略掉了。
结论:实际上C++标准库中的一些设计不单单让我们方便使用,也能提升一些效率,与有些人想像的恰恰相反。

最后,建议cpps = cpps + "xxoo";这样的代码都应该写成 cpps += "xxoo";如果有很多这样的代码,或在循环中经常出现,那会提升不少效率。原因就不用我说了吧。

再最后说说c++这里一个不足的地方,刚前面说到容器分配了内存后就不会再缩小了,所以在上面的程序结束前,cpps里分配的大于10001字节的内存就一直霸占着不放手了。上面代码里cpps还是全局变量,所以只到程序运行结束,内存才会释放,这样的话,如果程序中有一次用到了100000001字节或更大,而其它时候都只是10001字节就够了,那么它自始自终要占着大于100000001字节的内存不放手!这是我一直觉得不爽的地方!

有相同的看法或不同看法的,都来说说吧!
...全文
278 点赞 收藏 17
写回复
17 条回复
yi19861209 2017年10月06日
回复 点赞
大尾巴猫 2017年10月04日
11楼已经说的挺明白了 再进一步分析下三个代码的不同 strcat(cs,"a"); 很明显,cs每次的长度都要计算,cs越长,一个个字符去读直到0,每次花的时间就越多。 cpps=cpps+"a"; cpps+"a"花的时间是每次固定,因为string的实现中,大部分内部都有变量保存长度。慢的是赋值这个操作,产生了临时对象并赋值。 cpps += "a" 或者用append()方法,不产生临时对象,效率很高的。 string内部的字符串在逐渐增大的时候,会有原先的内存不够,重新再申请内存并复制,也会额外消耗点时间。 在本例中,事先用string的capacity()指定足够大的容量,效率是最佳的。
回复 点赞
x_xx_xxx_xxxx 2017年09月28日
速度一事,平均来看,支持 C 的均速较快,和内部实现有关,不能单看某一函数在一种场景下的表现。c++也有它突出的一面,所以才有c++如今的地位。
回复 点赞
whut_lcy 2017年09月28日
引用 9 楼 phy0292 的回复:
[quote=引用 5 楼 zhao4zhong1 的回复:] 无profiler不要谈效率!!尤其在这个云计算、虚拟机、模拟器、CUDA、多核 、多级cache、指令流水线、多种存储介质、……满天飞的时代!
最上面的代码就是从你在别处的回贴里复制的。发这个贴也就是告诉你,你经常回贴,几乎每个贴都回,但回贴的质量却很差,大多数回贴人家说的是东,你说的是西,感觉你就是刷等级来的。真正的高手,要么像2楼3楼的回答那样是切入实际的,要么就是潜水N年也不说一句话的。[/quote] 赵老师的分数惊为天人,我等摩拜!
回复 点赞
真相重于对错 2017年09月28日
引用 楼主 phy0292 的回复:
【C++】与【C】哪个慢,要看具体的代码
【】里面可以用任意语言替换
回复 点赞
das白 2017年09月27日
学习学习
回复 点赞
phy0292 2017年09月27日
我觉得在写应用程序的时候,总体上来说用C++要比C更好,对整个框架的设计、性能、维护等等都会带来好处。当然高手用C写出的程序和俗手用C++写出来的程序是无法相比的,因为起跑线不同。
回复 点赞
LQm36775683 2017年09月27日
这和代码相关吧
回复 点赞
赵4老师 2017年09月27日
无profiler不要谈效率!!尤其在这个云计算、虚拟机、模拟器、CUDA、多核 、多级cache、指令流水线、多种存储介质、……满天飞的时代!
回复 点赞
paschen 2017年09月27日
封装越深开销自然也就越大,string是在char*字符串上的封装,故通常效率是不如直接操作char*字符串的。但对于一些特殊操作,string效率可能更高,比如string实现时如果添加了一个成员变量记录字符串长度,每次追加一个字符时它可以直接知道在哪个位置添加,相反,strcat则需要每次计算字符串的长度,以确定追加的位置
回复 点赞
bravery36 2017年09月27日
据说c++ 11有个shrink_to_fit,没用过,应该也能释放内存。 strcat的确慢,但是C还可以单个赋值啊,cs[j]='a'; 所以快和慢肯定是要看怎么用。
回复 点赞
bravery36 2017年09月27日
string应该可以用swap交换一个栈的对象来释放大小,虽然一般而言这种做法是没有任何意义的。
回复 点赞
赵4老师 2017年09月27日
理解讨论之前请先学会如何观察
回复 点赞
赵4老师 2017年09月27日
引用 9 楼 phy0292 的回复:
[quote=引用 5 楼 zhao4zhong1 的回复:] 无profiler不要谈效率!!尤其在这个云计算、虚拟机、模拟器、CUDA、多核 、多级cache、指令流水线、多种存储介质、……满天飞的时代!
最上面的代码就是从你在别处的回贴里复制的。发这个贴也就是告诉你,你经常回贴,几乎每个贴都回,但回贴的质量却很差,大多数回贴人家说的是东,你说的是西,感觉你就是刷等级来的。真正的高手,要么像2楼3楼的回答那样是切入实际的,要么就是潜水N年也不说一句话的。[/quote] 你现在只是不懂而已。 等你真正懂了之后,你就会觉得我说的才是正确的。 不知者不怪。
回复 点赞
phy0292 2017年09月27日
引用 5 楼 zhao4zhong1 的回复:
无profiler不要谈效率!!尤其在这个云计算、虚拟机、模拟器、CUDA、多核 、多级cache、指令流水线、多种存储介质、……满天飞的时代!
最上面的代码就是从你在别处的回贴里复制的。发这个贴也就是告诉你,你经常回贴,几乎每个贴都回,但回贴的质量却很差,大多数回贴人家说的是东,你说的是西,感觉你就是刷等级来的。真正的高手,要么像2楼3楼的回答那样是切入实际的,要么就是潜水N年也不说一句话的。
回复 点赞
phy0292 2017年09月27日
引用 2 楼 bravery36 的回复:
string应该可以用swap交换一个栈的对象来释放大小,虽然一般而言这种做法是没有任何意义的。
swap也用过,但感觉挺猥琐的,shrink_to_fit这个看起来有点意思,C11加的东西好多,到现在还不知道c11一共增加和修改了多少东西。 另外你说的cs[j]='a'; 开头我也这样优化了下,后来我觉得这样不通用,比如如果代码是strcat(cs,"abcdefg123456"); 或更长的字符串呢,并且上面的代码里是知道了字符串要加在哪个位置,大多数情况事先是根本不知道末尾在哪的。
回复 点赞
fly0413 2017年09月27日
C块。 汇编更快。 1111 这个更更快。
回复 点赞
发动态
发帖子
C++ 语言
创建于2007-09-28

3.1w+

社区成员

24.8w+

社区内容

C++ 语言相关问题讨论,技术干货分享
社区公告
暂无公告