【问题标题】:What things (or in what cases) can make C++ slower than C ?什么事情(或在什么情况下)可以使 C++ 比 C 慢?
【发布时间】:2012-06-02 06:09:12
【问题描述】:

这是一个面试问题,面试已经完成。

什么东西可以让 C++ 比 C 慢?

面试官问的很深,每次我说什么的时候总是问“还有别的吗?”。

我的想法:

C 中不可用的 C++ 功能可能需要一些成本。

例如,如果我们在构造函数中使用赋值来初始化类的成员而不是通过初始化列表,则成员的默认构造函数可能会在构造函数的主体之前调用一次,然后该值被赋值清除。

需要通过查找虚函数指针来调用虚函数。这是一个开销。

还有更好的主意吗?

任何帮助将不胜感激。

谢谢!!!

【问题讨论】:

  • 我的回答是:“不要在那里工作”。但这不是真正的主题,所以我会保留它作为评论。
  • 虚函数没有比c慢的,c没有可以比较的概念。你得到的最接近的是函数指针,如果没有在 c 中实现 vtable,它们不支持一半的虚函数。
  • @EtiennedeMartel:你的评论“不要在那里工作”让我很感兴趣!为什么不呢? (您的回答可以帮助我避免不幸的职业道路错误,因此非常感谢您对它的关注。)
  • @thb 来Lounge<C++>,它会更适合这种讨论。
  • 请重新打开。对于这个问题,我有一个大而有效的答案,可以以非争论的方式回答,我无法提交,因为在我写作时它已关闭。

标签: c++ c exception polymorphism virtual


【解决方案1】:

哇...答案中对 C++ 的热爱,所以我会咆哮一下作为魔鬼的拥护者。

在原子语言范围内,我同意在 C++ 中执行很少或没有本质上显着“慢”。在更高的层次上,它变得复杂。 C++ 是一个有用的工具,但经常被panache 不恰当地作为解决所有问题的方法。当我们使用最简单的语言来描述问题时会更好,有时是 C++ 其他时候......汇编、操作码、解释语言。

C++ 在更大程度上依赖于编译器来“解释”意图,在模板、类、宏等的许多层中爬行,并进行多次迭代。通过翻译的每个循环都有可能遇到law of unintended consequences。据我所知,处理器没有本地处理 C++ 结构的寄存器或操作码,因此每个都必须分解为简化的部分。在这方面,编译器和代码标准是王道。在某些情况下,这在哲学上相当于拥有数学博士学位的老师(编译器)教三年级学生(处理器)。

我喜欢 C++ 并保守地使用它,但多年来我很少看到它写得很好。我想强迫一些人查看最终由构建反刍的程序集或机器代码,直到他们了解它是多么复杂。糟糕的 C 是一回事,糟糕的 C++ 可能会成倍恶化。

面试的更好答案...“您的团队何时会认为 C++ 不是问题的答案?”

【讨论】:

    【解决方案2】:

    什么都没有。事实上,C++ 比 C 快。有没有比较过 std::sortqsort

    人们说调用虚函数需要时间。他们是这样。但是 C 中的等效于在 vtable 中查找也是如此。如果您用两种语言编写等效的逻辑,C++ 版本将更易于维护、更简洁、更快。

    编辑:哦,是的,您可以根据需要从 C++ 调用 printf,或者如果您愿意,可以完全重新执行流实现。

    我是否提到过由于放错 NULL 终止符而崩溃的程序的性能是相当无关紧要的?

    宏和内联函数将“膨胀”一个 C 可执行文件,就像 C++ 中的模板一样。

    【讨论】:

    • @nhahtdh 抱怨 iostream 性能的人经常用错了。使用 std::endl 而不是 "\n" 并保持流与 cstdio 同步是一些最常见的错误。
    • C++ 本质上比 C 快:通过模板的 C++ 编译时泛型不一定比通过函数指针的 C 运行时泛型快,因为一个好的优化器可以平衡游戏按功能克隆的字段(即创建内联虚拟调用的专用版本);当然,只有当函数在翻译单元中可用时才有可能:请参阅news.ycombinator.com/item?id=3717463 了解如何从qsort()bitbucket.org/cggaertner/listsort 获得C 中优于std::forward_list::sort() 的自定义排序算法的C++ 可比性能跨度>
    • 这个答案是完全错误的。可以使用模板完成的任何事情都可以使用 C 中的内联函数和可能的宏来完成。但更重要的是,OP 询问 哪些 C++ 特性会使 C++ 变慢,这是一个非常好的问题,如果您想编写 C++,但不希望您的代码刻板地缓慢而臃肿。
    • 我来这里是为了给出严肃的答案,不要参与琐碎的语言战争。我不怀疑,拥有足够先进的工具链和编写代码的聪明人,您可以使用 C++ 生成快速、不臃肿的程序。但很明显,大量(我会说绝大多数)C++ 程序非常缓慢和臃肿。一个很好的例子是替代 malloc 实现的所有工作,以弥补 KDE 的速度慢(主要是由于我在回答中解释的第一个问题)。
    • 您关于“安全”和“不安全”的论点是题外话,并且会分散问题的注意力。面试问题不是关于语言的相对安全性(这通常被夸大了;查看 JS、PHP 等代码中的所有漏洞,即使它们应该是没有指针的“安全”语言)。这是关于速度的。
    【解决方案3】:

    C++ 与 C 相比本质上并没有什么慢的地方,但是,在执行相同任务时,惯用的 C++ 代码往往比惯用的 C 代码更慢、更重。惯用语是这里的关键;如果您编写 C 代码来执行任务的方式与在 C++ 中执行该任务的方式完全相同,那么它会同样慢。另一方面,如果您知道 C++ 中的隐藏成本通常会在哪里蔓延,则可以努力将其保持在最低限度,并在不增加成本的情况下获得 C++ 的好处。

    首先是动态内存分配。在 C 中,您会看到您所做的每一点动态内存分配,因为它都是显式的(以调用 malloc 的形式或调用返回分配对象的第三方库函数的形式)。在 C++ 中,许多对象的存储持续时间是自动的类对象仍然会由于其构造函数中发生隐藏分配而导致动态内存分配。一个好的 C++ STL(或第三方库)实现可以通过在对象本身中包含小缓冲区来避免大量此类成本,并且仅在需要大缓冲区时执行动态分配,但实际上很少这样做。 (如果我没记错的话,llvm 的 libc++ 会,但 GCC 的 libstdc++ 不会。)由于这是一个实施质量问题,通常不在您自己的代码的控制范围内,您可以在此处做的主要事情是尽量减少影响是意识到自动对象分配动态内存的可能性,并避免创建超出您需要的内容(例如,尽可能使用指针或引用)。这对您的代码也有其他好处。

    另一个大领域是字符串处理。在惯用的 C 语言中,字符串是用snprintf 或类似的方式一举构建的。在 C++ 和许多其他具有更强大的字符串类/类型的语言中,字符串的连接(逐段构造)是惯用的。这是非常低效的,导致多个分配/解除分配步骤、副本等,更不用说由此产生的内存碎片了。我不确定 C++ 的最佳实践会涉及哪些内容(我并不精通 C++),但应该有办法将这种影响降到最低。

    当然,最普遍的是隐藏代码。这是一个包罗万象的东西。在 C++ 中编写代码很容易,许多你从未见过的额外代码被执行。构造函数/析构函数、重载的运算符和模板是最明显的原因。同样,如果您想在 C 中以相同的方式做事,成本将是相同的,但不同之处在于,在 C 中,您会立即看到成本,因为你必须自己写。

    【讨论】:

    • 您的很多答案都与 C++ 无关,而与标准是通用的,而不是特定的这一事实有关。例如,如果需要,您可以编写一个基于表达式模板的字符串类,该类可以完全等同于手动 C 语言,只需一个分配步骤。如果您确实对标准组件有性能问题,只需推出一个满足您更具体需求的组件。这不是什么奇妙的图书馆,它只是入门并涵盖大多数用途。 snprintf 和朋友们非常不安全,以至于他们并不是一个真正的公平比较。
    • 我完全同意你可以这样做;我的观点是,如果你想避免在 C++ 中使事情变得昂贵的陷阱,你可能必须这样做。至于你对snprintf 的评论,这已经是你第三次毫无理由地指责某事“不安全”了,而且这是题外话,近乎拖钓。
    • 您所要做的就是查看界面。通过sizeof(src) 而不是sizeof(dest),你肯定会遇到一个讨厌的问题——更不用说你通过写十亿次违反了 DRY 的事实。或者编写格式说明符,然后在更改变量类型时不更新它。或者有太多的格式说明符。或者不小心手动覆盖了 NULL 终止符。所有这些错误都是接口允许的,并且发生在真正的 C 程序中。但是,使用 C++ 标准字符串处理不可能犯同样的错误。
    • 顺便说一句,std::string 的几乎所有实际使用都是不安全的,因为分配失败的异常没有得到妥善处理。您可能会称其为“只是 DoS”,但如果文件处于不一致的状态,也可能导致进一步的妥协。我同意您可以使其安全(在编写文件时使您的应用程序对异步终止具有健壮性,或者正确处理异常),但是如果您这样做一万次,无论您多么有经验,您都会犯错误。看看这个论点听起来有多愚蠢?
    • 不。你只需要在你的析构函数中写一个清理,然后它为什么被调用就无关紧要了。您不必在使用它的每个地方都编写它(与使用 snprintf 安全不同)。更相关的是,如果您用完虚拟内存,一大堆操作系统例程和其他东西可能无法工作,因此在这种情况下,无论您编写什么用户模式代码,都很难执行任何有用的工作。异常安全不是你在每个函数中都写的东西,它是你设计一次然后就完成的东西。
    【解决方案4】:

    先验不是性能问题,但 LLVM 代码库既不使用 RTTI 也不使用异常,因为它们被视为 too costly in terms of code size

    【讨论】:

    • 请注意,在正确使用 C++ 的 C++ 代码中(例如,使用 STL 和大多数非平凡的类),不可能没有例外地编写正确的程序。这是因为在构造函数中发生的分配可能会失败,并且通常报告和处理没有崩溃和烧毁的失败的唯一方法是通过异常。
    • 它是错误的。您可以使用 nothrow new 进行分配,甚至使用 malloc,您不需要异常也可以编写正确的代码。
    【解决方案5】:

    还有更好的主意吗?任何帮助将不胜感激。

    C++ 中的 STL 很少比 C 中专门编码的等价物慢。但是,STL 的便利性有时会导致编写速度较慢的代码。例如,假设有一组固定的 100 个项目,其中有 10 个或 15 个可变选择。假设程序的时间关键循环多次询问项目i 是否已被选中。支持这种时间关键循环的快速数据结构将是 100 个布尔值的数组(或向量等)。但是,填充 std::set&lt;size_t&gt; 可能比填充数组更容易用 C++ 编写代码。出于这个原因,C++ 程序员可能更喜欢集合而不是数组。

    当然,较慢的代码是否是一个问题取决于时间关键循环将看到多少服务。如果对数组技术进行编程需要额外的半小时,并且程序生命周期内的总执行时间节省为 0.5 秒,那么 set 技术可能更可取。另一方面,如果总执行时间节省 30 天,那么数组技术可能更可取。

    可能会给出许多类似的答案。祝你面试顺利。

    【讨论】:

    • 您不能拥有一个包含 100 个条目的 std::set&lt;bool&gt;booltruefalse只有2个值,所以最多只能包含2个值。
    • 应该编辑成vector,但是bitset (STL) 对于示例问题来说是一个更快的解决方案(可能比char数组更好)。
    • @DeadMG 也许他的意思是多集;-)
    • @DeadMG:你是对的,当然。谢谢。我的意思是std::set&lt;size_t&gt;。现在编辑答案。
    【解决方案6】:

    C 有 restrict,C++ 没有,尽管大多数编译器都将它作为扩展。

    还有 C++ 没有的可变长度数组。

    【讨论】:

    • 基于堆的内存区域可以比alloca更快地分配。
    • @DeadMG:取决于实现。在带有帧指针的情况下,alloca(n) 纯粹是 __asm__ __volatile__ ( "sub %1,%%esp ; mov %%esp, %0" : "=r"(result) : "g"(n)); 或等效项。
    【解决方案7】:

    C++ 中的大多数功能都是解决 C 中(潜在)问题的解决方案(例如:构造函数以确保创建的数据包的有效性(C 中的struct

    这意味着要在 C 中编写一个正确的程序以试图避免存在 C++ 特性的问题,您将必须执行 C++ 在幕后执行的类似操作。这导致两种情况下的性能相似。

    当然,您总是可以编写“更快”的草率程序,但并非在所有情况下都能正常工作

    【讨论】:

    • 或者 C 的性能更差,因为您不太可能足够好地优化 C 版本。
    • 或更快的性能,因为您的代码根本不需要 std 旨在解决的解决方案。
    猜你喜欢
    • 2011-04-20
    • 2014-09-13
    • 2020-12-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-30
    • 2017-11-30
    • 2021-09-02
    相关资源
    最近更新 更多