Kir*_*rov 27 c c++ performance coding-style
C++风格与性能 - 使用C风格的东西,比一些C++等价物更快,这是不好的做法?例如:
不要使用atoi(),itoa(),atol()等!使用std::stringstream< - 有时可能更好,但总是如此?使用C函数有什么不好的?是的,C风格,而不是C++,但不管怎样?这是C++,我们一直在寻找性能..
永远不要使用原始指针,而是使用智能指针 - 好吧,它们真的很有用,每个人都知道,我知道,我一直使用它,我知道它们是多少更好的原始指针,但有时它是完全安全的使用原始指针..为什么不呢?"不是C++风格?< - 这够了吗?
不要使用按位操作 - 太C风格?WTH?为什么不,当你确定你在做什么?例如 - 不要按位交换变量(a ^= b; b ^= a; a ^= b;) - 使用标准的3步交换.不要使用左移乘以2.等等......(好吧,这不是C++风格与C风格,但仍然是"不好的做法")
最后,最昂贵的 - "不要使用enum-s来返回代码,它也是C风格,使用异常来处理不同的错误"?为什么?好吧,当我们谈论深层次的错误处理时 - 好的,但为什么总是?这有什么不对,例如 - 当我们谈论一个函数时,它返回不同的错误代码,当错误处理只在函数中实现时,它会调用第一个错误代码?我的意思是 - 不需要在上层传递错误代码.例外是相当缓慢的,它们是特殊情况的例外,而不是美丽.
等等等
好的,我知道良好的编码风格非常非常重要 < - 代码应该易于阅读和理解.我知道微优化并不需要,因为现代编译器非常智能,编译器优化非常强大.但我也知道异常处理有多昂贵,如何实现(某些)smart_pointers,并且一直没有需要smart_ptr ......例如,我知道这atoi不是那么"安全" std::stringstream,但仍然如此.那表现怎么样?
编辑:我不是在谈论一些非常困难的东西,它们只是C风格的特定.我的意思是 - 不要错过使用函数指针或虚拟方法以及C++程序员可能不知道的这些东西,如果从未使用过这类东西(而C程序员一直这样做).我正在谈论一些更常见和更容易的事情,例如在示例中.
jal*_*alf 46
一般来说,你缺少的是C方式通常不会更快.它看起来更像是一个黑客,人们常常认为黑客攻击速度更快.
永远不要使用原始指针,而是使用智能指针 - 好吧,它们真的很有用,每个人都知道,我知道,我一直使用它,我知道它们是多少更好的原始指针,但有时它是完全安全的使用原始指针..为什么不呢?
让我们把问题放在首位.有时使用原始指针是安全的.是否仅仅是使用它们的理由?有什么关于原始指针实际上优于智能指针?这取决于.一些智能指针类型比原始指针慢.其他人不是.在a std::unique_ptr或a上使用原始指针的性能原理是boost::scoped_ptr什么?它们都没有任何开销,它们只是提供更安全的语义.
这并不是说你永远不应该使用原始指针.只是因为你认为你需要表现,或者只是因为"看起来很安全",你不应该这样做.当你需要表示智能指针不能代表的东西时,这样做.根据经验,使用指针指向事物,使用智能指针来获取事物的所有权.但这是一个经验法则,而不是普遍的规则.使用适合手头任务的任何一种.但是不要盲目地假设原始指针会更快.当你使用智能指针时,一定要熟悉它们.太多人只是shared_ptr用于所有事情,这在表现和非常模糊的共享所有权语义方面都非常糟糕,最终应用于所有事情.
不要使用按位操作 - 太C风格?WTH?为什么不,当你确定你在做什么?例如 - 不要按比例交换变量(a ^ = b; b ^ = a; a ^ = b;) - 使用标准的3步交换.不要使用左移乘以2.等等......(好吧,这不是C++风格与C风格,但仍然是"不好的做法")
那个是正确的.原因是"它更快".按位交换在很多方面都存在问题:
当乘以2时,乘以2.编译器知道这个技巧,如果它更快,它将应用它.再次,转移有许多相同的问题.在这种情况下,它可能更快(这就是编译器为你做的原因),但它仍然更容易出错,并且它适用于一组有限的类型.特别是它可能会编译很好的类型,你认为这样做是安全的...然后在实践中炸毁.特别是,负值上的位移是一个雷区.让编译器为您导航.
顺便说一下,这与"C风格"无关.完全相同的建议适用于C.在C中,常规交换仍然比按位黑客更快,如果编译器有效且速度更快,编译器仍将完成位移而不是乘法.
但作为一名程序员,你应该只对一件事使用按位运算:按位操作整数.你已经有了一个乘法运算符,所以当你想要乘法时使用它.你也有一个std::swap功能.如果要交换两个值,请使用它.优化的一个最重要的技巧可能是令人惊讶的是,编写可读的,有意义的代码.这允许您的编译器理解代码并对其进行优化.std::swap可以专门为其使用的特定类型进行最有效的交换.并且编译器知道实现乘法的几种方法,并且将根据情况选择最快的...如果你告诉它.如果你告诉它改变位移,你只是误导它.告诉它倍增,它会给你最快的倍数.
最后,最昂贵的 - "不要使用enum-s来返回代码,它也是C风格,使用异常来处理不同的错误"?
取决于你问谁.我所知道的大多数C++程序员都找到了两者的空间.但请记住,关于返回码的一个不幸的事情是它们很容易被忽略.如果这是不可接受的,那么在这种情况下你可能更喜欢例外.另一点是RAII与异常一起工作得更好,而C++程序员应该尽可能使用RAII.不幸的是,因为构造函数不能返回错误代码,所以异常通常是指示错误的唯一方法.
但仍然......性能怎么样?
怎么样?任何体面的C程序员都乐意告诉你不要过早优化.
您的CPU每秒可执行大约80亿条指令.如果你std::stringstream在第二次拨打两个电话,这是否会在预算中产生可衡量的影响?
你无法预测表现.您无法编写能够快速生成代码的编码指南.即使您从未抛出任何异常,也从未使用过stringstream,您的代码仍然不会自动快速.如果您在编写代码时尝试进行优化,那么您将花费90%的精力优化90%的几乎不执行的代码.为了获得可衡量的改进,您需要关注构成95%执行时间的代码的10%.试图让一切变得快速只会导致浪费大量时间,而且很难显示出来,而且代码库更加丑陋.
Jer*_*fin 14
atoi,而且atol通常不仅仅是基于风格.它们使得检测输入错误基本上不可能.虽然a stringstream 可以做同样的工作,但strtol(例如)是我通常建议的直接替代品.总而言之,听起来像你已经得到了一些教条,以至于无视现实.它可能是最好的忽略或至少被视为一个相当极端位置有关C++怎么能写,不一定怎么老是(或曾经,必然)应该被写入.
加入@Jerry Coffin的答案,我认为这个答案非常有用,我想提出一些主观意见.
问题在于程序员往往会喜欢它.也就是说,我们大多数人都非常喜欢为了它而编写精美的代码.只要您自己完成项目,这就完全没问题了.请记住,一个好的软件是二进制代码按预期工作的软件,而不是源代码干净的软件.然而,当涉及由很多人开发和维护的大型项目时,编写更简单的代码在经济上更好,这样团队中的任何人都没有时间去理解你的意思.即使以运行时为代价(自然成本较低).这就是为什么许多人,包括我自己,都会劝阻使用xor技巧而不是赋值(你可能会感到惊讶,但是有很多程序员没有听说过xor技巧).xor技巧无论如何只适用于整数,
使用itoa,atoi等代替流更快.是的.但要快多少?不多.除非您的大多数程序只进行从文本到字符串的转换,否则您将不会注意到差异.人们为什么要使用itoa,atoi等?好吧,有些人这样做,因为他们不知道c ++替代品.另一组是因为它只是一个LOC.对于前一组 - 对你的耻辱,为后者 - 为什么不提升:: lexical_cast?
例外......啊......是的,它们可能比返回码慢,但在大多数情况下并不是真的.返回代码可以包含信息,这不是错误.应该使用例外来报告严重错误,这些错误是不容忽视的.有些人忘记了这一点,并使用异常来模拟一些奇怪的信号/插槽机制(相信我,我已经看到了,哎呀).我的个人意见是使用返回代码没有任何问题,但是应该报告严重的错误,除非分析器已经证明避免使用它们会大大提高性能
原始指针 - 我自己的意见是:当它不是关于所有权时,永远不要使用智能指针.当涉及所有权时,始终使用智能指针.当然有一些例外.
位移而不是乘以2的幂.我相信,这是过早优化的典型例子.x << 3;我敢打赌,至少25%的同事需要一些时间才能理解/意识到这一点x * 8; 混淆(至少25%)代码的具体原因是什么?再次,如果分析器告诉你这是瓶颈(我怀疑极少数情况会是这种情况),那么绿灯,继续这样做(留下评论其实这意味着x * 8)
把它们加起来.一个优秀的专业人士承认"好的风格",理解他们为什么以及什么时候好,并且理所当然地例外,因为他知道他在做什么.平均/差的专业人员分为两类:第一类不承认好风格,甚至不了解它是什么和为什么.解雇他们.另一种类型将风格视为教条,这并不总是好的.